@codemirror/view#PluginValue TypeScript Examples
The following examples show how to use
@codemirror/view#PluginValue.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts From obsidian-banners with MIT License | 5 votes |
getViewPlugin = (plugin: BannersPlugin) => ViewPlugin.fromClass(class BannerPV implements PluginValue {
decor: DecorationSet;
constructor(view: EditorView) {
this.decor = this.decorate(view.state);
}
update(_update: ViewUpdate) {
const { docChanged, view, state, startState } = _update;
if (docChanged || state.facet(bannerDecorFacet) !== startState.facet(bannerDecorFacet) || state.facet(iconDecorFacet) !== startState.facet(iconDecorFacet)) {
this.decor = this.decorate(view.state);
}
}
decorate(state: EditorState): DecorationSet {
// If there's no YAML, stop here
const cursor = syntaxTree(state).cursor();
cursor.firstChild();
if (cursor.name !== YAML_SEPARATOR_TOKEN) { return Decoration.none }
// Get all frontmatter fields to later process
const frontmatter: {[key: string]: string} = {};
let key;
while (cursor.nextSibling() && cursor.name !== YAML_SEPARATOR_TOKEN) {
const { from, to, name } = cursor;
if (name === YAML_DEF_NAME_TOKEN) {
key = state.sliceDoc(from, to);
} else if (YAML_DEF_VAL_TOKENS.includes(name) && !frontmatter[key]) {
const isStr = name === YAML_DEF_STR_TOKEN;
const val = state.sliceDoc(from + (isStr ? 1 : 0), to - (isStr ? 1 : 0));
frontmatter[key] = val;
}
};
const bannerData = plugin.metaManager.getBannerData(frontmatter);
const { src, icon } = bannerData;
const { contentEl, file } = state.field(editorViewField);
const widgets: Decoration[] = [];
// Add banner widgets if applicable
if (src) {
const settingsFacet = state.facet(bannerDecorFacet);
widgets.push(
Decoration.widget({ widget: new BannerWidget(plugin, bannerData, file.path, contentEl, settingsFacet) }),
Decoration.widget({ widget: new SpacerWidget() }),
Decoration.line({ class: 'has-banner' })
);
}
// Add icon widget if applicable
if (icon) {
const settingsFacet = state.facet(iconDecorFacet);
widgets.push(
Decoration.widget({ widget: new IconWidget(plugin, icon, file, settingsFacet) }),
Decoration.line({ class: "has-banner-icon", attributes: { "data-icon-v": settingsFacet.iconVerticalAlignment }})
);
}
return Decoration.set(widgets.map(w => w.range(0)), true);
}
}, {
decorations: v => v.decor
})
Example #2
Source File: index.ts From codemirror-languageserver with BSD 3-Clause "New" or "Revised" License | 4 votes |
class LanguageServerPlugin implements PluginValue {
public client: LanguageServerClient;
private documentUri: string;
private languageId: string;
private documentVersion: number;
private changesTimeout: number;
constructor(private view: EditorView) {
this.client = this.view.state.facet(client);
this.documentUri = this.view.state.facet(documentUri);
this.languageId = this.view.state.facet(languageId);
this.documentVersion = 0;
this.changesTimeout = 0;
this.client.attachPlugin(this);
this.initialize({
documentText: this.view.state.doc.toString(),
});
}
update({ docChanged }: ViewUpdate) {
if (!docChanged) return;
if (this.changesTimeout) clearTimeout(this.changesTimeout);
this.changesTimeout = self.setTimeout(() => {
this.sendChange({
documentText: this.view.state.doc.toString(),
});
}, changesDelay);
}
destroy() {
this.client.detachPlugin(this);
}
async initialize({ documentText }: { documentText: string }) {
this.client.textDocumentDidOpen({
textDocument: {
uri: this.documentUri,
languageId: this.languageId,
text: documentText,
version: this.documentVersion,
}
});
}
async sendChange({ documentText }: { documentText: string }) {
if (!this.client.ready) return;
try {
await this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
},
contentChanges: [{ text: documentText }],
});
} catch (e) {
console.error(e);
}
}
requestDiagnostics(view: EditorView) {
this.sendChange({ documentText: view.state.doc.toString() });
}
async requestHoverTooltip(
view: EditorView,
{ line, character }: { line: number; character: number }
): Promise<Tooltip | null> {
if (!this.client.ready || !this.client.capabilities!.hoverProvider) return null;
this.sendChange({ documentText: view.state.doc.toString() });
const result = await this.client.textDocumentHover({
textDocument: { uri: this.documentUri },
position: { line, character },
});
if (!result) return null;
const { contents, range } = result;
let pos = posToOffset(view.state.doc, { line, character })!;
let end: number;
if (range) {
pos = posToOffset(view.state.doc, range.start)!;
end = posToOffset(view.state.doc, range.end);
}
if (pos === null) return null;
const dom = document.createElement('div');
dom.classList.add('documentation');
dom.textContent = formatContents(contents);
return { pos, end, create: (view) => ({ dom }), above: true };
}
async requestCompletion(
context: CompletionContext,
{ line, character }: { line: number; character: number },
{
triggerKind,
triggerCharacter,
}: {
triggerKind: CompletionTriggerKind;
triggerCharacter: string | undefined;
}
): Promise<CompletionResult | null> {
if (!this.client.ready || !this.client.capabilities!.completionProvider) return null;
this.sendChange({
documentText: context.state.doc.toString(),
});
const result = await this.client.textDocumentCompletion({
textDocument: { uri: this.documentUri },
position: { line, character },
context: {
triggerKind,
triggerCharacter,
}
});
if (!result) return null;
const items = 'items' in result ? result.items : result;
let options = items.map(
({
detail,
label,
kind,
textEdit,
documentation,
sortText,
filterText,
}) => {
const completion: Completion & {
filterText: string;
sortText?: string;
apply: string;
} = {
label,
detail,
apply: textEdit?.newText ?? label,
type: kind && CompletionItemKindMap[kind].toLowerCase(),
sortText: sortText ?? label,
filterText: filterText ?? label,
};
if (documentation) {
completion.info = formatContents(documentation);
}
return completion;
}
);
const [span, match] = prefixMatch(options);
const token = context.matchBefore(match);
let { pos } = context;
if (token) {
pos = token.from;
const word = token.text.toLowerCase();
if (/^\w+$/.test(word)) {
options = options
.filter(({ filterText }) =>
filterText.toLowerCase().startsWith(word)
)
.sort(({ apply: a }, { apply: b }) => {
switch (true) {
case a.startsWith(token.text) &&
!b.startsWith(token.text):
return -1;
case !a.startsWith(token.text) &&
b.startsWith(token.text):
return 1;
}
return 0;
});
}
}
return {
from: pos,
options,
};
}
processNotification(notification: Notification) {
try {
switch (notification.method) {
case 'textDocument/publishDiagnostics':
this.processDiagnostics(notification.params);
}
} catch (error) {
console.error(error);
}
}
processDiagnostics(params: PublishDiagnosticsParams) {
if (params.uri !== this.documentUri) return;
const diagnostics = params.diagnostics
.map(({ range, message, severity }) => ({
from: posToOffset(this.view.state.doc, range.start)!,
to: posToOffset(this.view.state.doc, range.end)!,
severity: ({
[DiagnosticSeverity.Error]: 'error',
[DiagnosticSeverity.Warning]: 'warning',
[DiagnosticSeverity.Information]: 'info',
[DiagnosticSeverity.Hint]: 'info',
} as const)[severity!],
message,
}))
.filter(({ from, to }) => from !== null && to !== null && from !== undefined && to !== undefined)
.sort((a, b) => {
switch (true) {
case a.from < b.from:
return -1;
case a.from > b.from:
return 1;
}
return 0;
});
this.view.dispatch(setDiagnostics(this.view.state, diagnostics));
}
}