From 4470bbbeb5594e9a7854b3427c0501a454a121dd Mon Sep 17 00:00:00 2001 From: Semih702 Date: Thu, 20 Aug 2026 16:16:19 +0300 Subject: [PATCH] Use @codemirror/lang-go for the Go editor Go was still on the legacy stream mode, which registers no `autocomplete` language data, so the editor had no completion sources for Go at all. `@codemirror/lang-go` adds local completion of parameters, variables and same-file functions, plus a Lezer parser for highlighting and indentation. --- .../misc/CodeMirror/languageCompartment.ts | 8 +-- package.json | 1 + .../CodeMirror/languageCompartment.test.ts | 56 +++++++++++++++++++ yarn.lock | 20 +++++++ 4 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 test/javascript/components/misc/CodeMirror/languageCompartment.test.ts diff --git a/app/javascript/components/misc/CodeMirror/languageCompartment.ts b/app/javascript/components/misc/CodeMirror/languageCompartment.ts index b3549cbf67..a5fb92eda4 100644 --- a/app/javascript/components/misc/CodeMirror/languageCompartment.ts +++ b/app/javascript/components/misc/CodeMirror/languageCompartment.ts @@ -27,6 +27,10 @@ export const loadLanguageCompartment = async ( const { gleam } = await import('@exercism/codemirror-lang-gleam') return compartment.of(gleam()) } + case 'go': { + const { go } = await import('@codemirror/lang-go') + return compartment.of(go()) + } case 'javascript': case 'typescript': { const { javascript } = await import('@codemirror/lang-javascript') @@ -158,10 +162,6 @@ export const loadLanguageCompartment = async ( const { apl } = await import('@codemirror/legacy-modes/mode/apl') return compartment.of(StreamLanguage.define(apl)) } - case 'go': { - const { go } = await import('@codemirror/legacy-modes/mode/go') - return compartment.of(StreamLanguage.define(go)) - } case 'groovy': { const { groovy } = await import('@codemirror/legacy-modes/mode/groovy') return compartment.of(StreamLanguage.define(groovy)) diff --git a/package.json b/package.json index 700a4f3cae..021f62ec94 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@codemirror/commands": "^6.7.1", "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-go": "^6.0.1", "@codemirror/lang-java": "^6.0.1", "@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-php": "^6.0.1", diff --git a/test/javascript/components/misc/CodeMirror/languageCompartment.test.ts b/test/javascript/components/misc/CodeMirror/languageCompartment.test.ts new file mode 100644 index 0000000000..bfd31e0f99 --- /dev/null +++ b/test/javascript/components/misc/CodeMirror/languageCompartment.test.ts @@ -0,0 +1,56 @@ +import { EditorState } from '@codemirror/state' +import type { CompletionSource } from '@codemirror/autocomplete' +import { CompletionContext } from '@codemirror/autocomplete' +import { syntaxTree } from '@codemirror/language' +import { loadLanguageCompartment } from '@/components/misc/CodeMirror/languageCompartment' + +const GO_EXERCISE = [ + 'package main', + '', + 'func Welcome(name string) string {', + '\treturn na', + '}', +].join('\n') + +async function goState() { + return EditorState.create({ + doc: GO_EXERCISE, + extensions: [await loadLanguageCompartment('go')], + }) +} + +// Candidates come from the sources a language registers as `autocomplete` +// language data, which is what the legacy Go stream mode was missing. +async function completionsAt(state: EditorState, pos: number) { + const sources = state.languageDataAt('autocomplete', pos) + const options = [] + + for (const source of sources) { + const result = await source(new CompletionContext(state, pos, false)) + + if (result) options.push(...result.options) + } + + return options +} + +test('go suggests names declared in the file', async () => { + const state = await goState() + const options = await completionsAt(state, GO_EXERCISE.indexOf('na\n') + 2) + + expect(options).toContainEqual( + expect.objectContaining({ label: 'name', type: 'var' }) + ) +}) + +test('go parses without errors', async () => { + const errors: string[] = [] + + syntaxTree(await goState()).iterate({ + enter: (node) => { + if (node.type.isError) errors.push(node.name) + }, + }) + + expect(errors).toEqual([]) +}) diff --git a/yarn.lock b/yarn.lock index 50a14353fe..bac92d84de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1102,6 +1102,17 @@ "@lezer/common" "^1.0.2" "@lezer/css" "^1.1.7" +"@codemirror/lang-go@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-go/-/lang-go-6.0.1.tgz#598222c90f56eae28d11069c612ca64d0306b057" + integrity sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.6.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/go" "^1.0.0" + "@codemirror/lang-html@^6.0.0": version "6.4.9" resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.4.9.tgz#d586f2cc9c341391ae07d1d7c545990dfa069727" @@ -1854,6 +1865,15 @@ "@lezer/highlight" "^1.0.0" "@lezer/lr" "^1.0.0" +"@lezer/go@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/go/-/go-1.0.1.tgz#3004b54f5e4c9719edcba98653f380baf8c0d1a2" + integrity sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ== + dependencies: + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.3.0" + "@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3", "@lezer/highlight@^1.2.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.1.tgz#596fa8f9aeb58a608be0a563e960c373cbf23f8b"