Skip to content

Commit

Permalink
Add diagnosticRenderer, and option overrides to public API of codemir…
Browse files Browse the repository at this point in the history
…ror-language-client
  • Loading branch information
charlespwd committed Oct 19, 2023
1 parent 23c84af commit 0ca7963
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-seas-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/codemirror-language-client': minor
---

Add `diagnosticRenderer` and option overrides to public API of CodeMirrorLanguageClient
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
liquidHTMLCompletionExtension,
InfoRenderer,
infoRendererFacet,
AutocompleteOptions,
LinterOptions,
DiagnosticRenderer,
diagnosticRendererFacet,
} from './extensions';

/**
Expand Down Expand Up @@ -49,6 +53,25 @@ export interface CodeMirrorDependencies {
* A function that takes a completion object and returns a DOM node.
*/
infoRenderer?: InfoRenderer;

/**
* The diagnosticRenderer is a function that returns a DOM node that
* contains the content of a diagnostic. It overrides the default
* rendering logic for diagnostics.
*/
diagnosticRenderer?: DiagnosticRenderer;

/**
* Say you wanted to change the settings of the `autocomplete` extension,
* you'd do it with that.
*/
autocompleteOptions?: AutocompleteOptions;

/**
* Say you wanted to change the settings of the `linter` extension,
* you'd do it with that.
*/
linterOptions?: LinterOptions;
}

// There is one LanguageClient
Expand All @@ -57,18 +80,29 @@ export interface CodeMirrorDependencies {
export class CodeMirrorLanguageClient {
private readonly client: LanguageClient;
private readonly infoRenderer: InfoRenderer | undefined;
private readonly diagnosticRenderer: DiagnosticRenderer | undefined;
private readonly autocompleteExtension: Extension;
private readonly linterExtension: Extension;

constructor(
private readonly worker: Worker,
{ log = defaultLogger }: ClientDependencies = {},
{ infoRenderer }: CodeMirrorDependencies = {},
{
infoRenderer,
diagnosticRenderer,
autocompleteOptions,
linterOptions,
}: CodeMirrorDependencies = {},
) {
this.client = new LanguageClient(worker, {
clientCapabilities,
log,
});
this.worker = worker;
this.infoRenderer = infoRenderer;
this.diagnosticRenderer = diagnosticRenderer;
this.autocompleteExtension = liquidHTMLCompletionExtension(autocompleteOptions);
this.linterExtension = lspLinter(linterOptions);
}

public async start() {
Expand All @@ -92,8 +126,9 @@ export class CodeMirrorLanguageClient {
fileUriFacet.of(fileUri),
textDocumentSync,
infoRendererFacet.of(this.infoRenderer),
diagnosticRendererFacet.of(this.diagnosticRenderer),
]
.concat(shouldLint ? lspLinter : [])
.concat(shouldComplete ? liquidHTMLCompletionExtension : []);
.concat(shouldLint ? this.linterExtension : [])
.concat(shouldComplete ? this.autocompleteExtension : []);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Module: completeLiquidHTML', () => {
fileUriFacet.of(fileUri),
textDocumentSync,
infoRendererFacet.of(infoRenderer),
liquidHTMLCompletionExtension,
liquidHTMLCompletionExtension(),
];
state = EditorState.create({
doc: 'hello world',
Expand Down
15 changes: 10 additions & 5 deletions packages/codemirror-language-client/src/extensions/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ import { Facet } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { TextDocument } from 'vscode-languageserver-textdocument';

export const liquidHTMLCompletionExtension = autocompletion({
activateOnTyping: true,
override: [completeLiquidHTML],
maxRenderedOptions: 20,
});
type FirstArgType<F> = F extends (arg: infer A) => any ? A : never;
export type AutocompleteOptions = Partial<FirstArgType<typeof autocompletion>>;

export const liquidHTMLCompletionExtension = (overrides: AutocompleteOptions = {}) =>
autocompletion({
activateOnTyping: true,
override: [completeLiquidHTML],
maxRenderedOptions: 20,
...overrides,
});

/**
* An InfoRenderer would be equivalent to the Quick Info window in VS Code. It's the part of the completion
Expand Down
14 changes: 11 additions & 3 deletions packages/codemirror-language-client/src/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
export { clientFacet, fileUriFacet } from './client';
export { textDocumentSync, textDocumentField } from './textDocumentSync';
export {
liquidHTMLCompletionExtension,
AutocompleteOptions,
InfoRenderer,
completeLiquidHTML,
infoRendererFacet,
InfoRenderer,
liquidHTMLCompletionExtension,
} from './complete';
export { lspLinter, diagnosticsFacet, lspDiagnosticsField } from './lspLinter';
export {
DiagnosticRenderer,
LinterOptions,
diagnosticRendererFacet,
diagnosticsFacet,
lspDiagnosticsField,
lspLinter,
} from './lspLinter';
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('Module: lspLinter', () => {

beforeEach(() => {
client = new MockClient();
extensions = [clientFacet.of(client), fileUriFacet.of(fileUri), lspLinter];
extensions = [clientFacet.of(client), fileUriFacet.of(fileUri), lspLinter()];
view = new EditorView({
state: EditorState.create({
doc: 'hello world',
Expand Down Expand Up @@ -60,6 +60,7 @@ describe('Module: lspLinter', () => {
from: 0,
to: 'hello'.length,
message: 'hello not accepted',
renderMessage: undefined,
severity: 'error',
};
expect(codeMirrorDiagnostics[0]).to.eql(expectedDiagnostic);
Expand Down
72 changes: 46 additions & 26 deletions packages/codemirror-language-client/src/extensions/lspLinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export const diagnosticsFacet = Facet.define<CodeMirrorDiagnostic[], CodeMirrorD
combine: (values) => values[0],
});

export type DiagnosticRenderer = (lspDiagnostic: LSPDiagnostic) => Node;

export const diagnosticRendererFacet = Facet.define<
DiagnosticRenderer | undefined,
DiagnosticRenderer | undefined
>({
combine: (values) => (values.length > 0 ? values[0] : undefined),
});

export const computedCodeMirrorDiagnosticsValueProvider = diagnosticsFacet.compute(
[textDocumentField, lspDiagnosticsField, lspDiagnosticsVersionField],
(state) => {
Expand All @@ -60,42 +69,51 @@ export const computedCodeMirrorDiagnosticsValueProvider = diagnosticsFacet.compu
}

const lspDiagnostics = state.field(lspDiagnosticsField);
return lspDiagnostics.map((diagnostic) => lspToCodeMirrorDiagnostic(diagnostic, doc));
const diagnosticRenderer = state.facet(diagnosticRendererFacet.reader);
return lspDiagnostics.map((diagnostic) =>
lspToCodeMirrorDiagnostic(diagnostic, doc, diagnosticRenderer),
);
},
);

export const diagnosticsLinter = linter(
(view) => {
const diagnostics = view.state.facet(diagnosticsFacet.reader);
return diagnostics;
},
{
delay: 100,
needsRefresh(update) {
const currVersion = update.state.field(lspDiagnosticsVersionField);
const prevVersion = update.startState.field(lspDiagnosticsVersionField);

// Checking against any kind of changes otherwise the squiggly line disappears!!
return (
update.geometryChanged ||
update.viewportChanged ||
update.heightChanged ||
update.focusChanged ||
update.docChanged ||
update.transactions[0]?.reconfigured ||
prevVersion !== currVersion
);
type SecondArgType<F> = F extends (_: any, second: infer A) => any ? A : never;

export type LinterOptions = SecondArgType<typeof linter>;

export const diagnosticsLinter = (overrides: LinterOptions) =>
linter(
(view) => {
const diagnostics = view.state.facet(diagnosticsFacet.reader);
return diagnostics;
},
},
);
{
delay: 100,
needsRefresh(update) {
const currVersion = update.state.field(lspDiagnosticsVersionField);
const prevVersion = update.startState.field(lspDiagnosticsVersionField);

// Checking against any kind of changes otherwise the squiggly line disappears!!
return (
update.geometryChanged ||
update.viewportChanged ||
update.heightChanged ||
update.focusChanged ||
update.docChanged ||
update.transactions[0]?.reconfigured ||
prevVersion !== currVersion
);
},
...overrides,
},
);
export const diagnosticsPlugin = ViewPlugin.fromClass(DiagnosticsPlugin);

export const lspLinter: Extension = [
export const lspLinter = (linterOptions: LinterOptions = {}): Extension => [
textDocumentField,
lspDiagnosticsField,
lspDiagnosticsVersionField,
computedCodeMirrorDiagnosticsValueProvider,
diagnosticsLinter,
diagnosticsLinter(linterOptions),
diagnosticsPlugin,
];

Expand All @@ -121,6 +139,7 @@ function lspToCodeMirrorSeverity(severity: LSPSeverity | undefined): CodeMirrorS
function lspToCodeMirrorDiagnostic(
lspDiagnostic: LSPDiagnostic,
textDocument: TextDocument,
diagnosticRenderer: DiagnosticRenderer | undefined,
): CodeMirrorDiagnostic {
const { range, message, severity } = lspDiagnostic;
const { start, end } = range;
Expand All @@ -129,6 +148,7 @@ function lspToCodeMirrorDiagnostic(
to: textDocument.offsetAt(end),
message,
severity: lspToCodeMirrorSeverity(severity),
renderMessage: diagnosticRenderer ? () => diagnosticRenderer(lspDiagnostic) : undefined,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/codemirror-language-client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"resolveJsonModule": false,
"lib": [
"es2019",
"DOM",
"webworker"
]
}
Expand Down

0 comments on commit 0ca7963

Please sign in to comment.