diff --git a/package-lock.json b/package-lock.json index c61fd01ac..135e38209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,17 @@ "@aws-sdk/client-sqs": "^3.828.0", "@aws-sdk/client-ssm": "^3.828.0", "@aws-sdk/credential-providers": "^3.828.0", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-java": "^6.0.2", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-rust": "^6.0.2", + "@codemirror/language": "^6.11.2", + "@codemirror/lint": "^6.8.5", + "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.0", "@flatten-js/interval-tree": "^1.1.3", @@ -1542,6 +1552,28 @@ "node": ">=14.21.3" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, "node_modules/@codemirror/lang-cpp": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", @@ -1551,6 +1583,62 @@ "@lezer/cpp": "^1.0.0" } }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "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" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", @@ -1564,10 +1652,25 @@ "style-mod": "^4.0.0" } }, - "node_modules/@codemirror/language/node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } }, "node_modules/@codemirror/state": { "version": "6.5.2", @@ -2321,6 +2424,11 @@ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, "node_modules/@lezer/cpp": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.3.tgz", @@ -2331,10 +2439,15 @@ "@lezer/lr": "^1.0.0" } }, - "node_modules/@lezer/cpp/node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } }, "node_modules/@lezer/highlight": { "version": "1.2.1", @@ -2344,10 +2457,25 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@lezer/highlight/node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", + "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } }, "node_modules/@lezer/lr": { "version": "1.4.2", @@ -2357,10 +2485,25 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@lezer/lr/node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", @@ -6378,53 +6521,6 @@ "@codemirror/view": "^6.0.0" } }, - "node_modules/codemirror/node_modules/@codemirror/autocomplete": { - "version": "6.18.6", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", - "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/codemirror/node_modules/@codemirror/commands": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", - "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, - "node_modules/codemirror/node_modules/@codemirror/lint": { - "version": "6.8.5", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", - "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.35.0", - "crelt": "^1.0.5" - } - }, - "node_modules/codemirror/node_modules/@codemirror/search": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", - "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/codemirror/node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" - }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", diff --git a/package.json b/package.json index 2f57d514b..768de5cf4 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,17 @@ "@aws-sdk/client-sqs": "^3.828.0", "@aws-sdk/client-ssm": "^3.828.0", "@aws-sdk/credential-providers": "^3.828.0", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-java": "^6.0.2", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-rust": "^6.0.2", + "@codemirror/language": "^6.11.2", + "@codemirror/lint": "^6.8.5", + "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.0", "@flatten-js/interval-tree": "^1.1.3", diff --git a/static/codemirror-mobile-editor.ts b/static/codemirror-mobile-editor.ts new file mode 100644 index 000000000..c71ef8c6d --- /dev/null +++ b/static/codemirror-mobile-editor.ts @@ -0,0 +1,261 @@ +// CodeMirror 6 implementation for mobile devices in Compiler Explorer +import {EditorView, basicSetup} from 'codemirror'; +import {EditorState, Compartment} from '@codemirror/state'; +import {cpp} from '@codemirror/lang-cpp'; +import {javascript} from '@codemirror/lang-javascript'; +import {python} from '@codemirror/lang-python'; +import {rust} from '@codemirror/lang-rust'; +import {go} from '@codemirror/lang-go'; +import {java} from '@codemirror/lang-java'; +import {Diagnostic, linter, lintGutter} from '@codemirror/lint'; + +import {ICompilerExplorerEditor, CompilerError, EditorOptions} from './editor-abstraction'; + +interface LanguageExtension { + name: string; + extension: () => any; +} + +// Language mappings for CodeMirror 6 +const LANGUAGE_EXTENSIONS: Record = { + 'cpp': {name: 'C++', extension: cpp}, + 'c': {name: 'C', extension: cpp}, // Use C++ for C (close enough) + 'cxx': {name: 'C++', extension: cpp}, + 'cc': {name: 'C++', extension: cpp}, + 'javascript': {name: 'JavaScript', extension: javascript}, + 'js': {name: 'JavaScript', extension: javascript}, + 'typescript': {name: 'TypeScript', extension: javascript}, // Use JS for TS + 'ts': {name: 'TypeScript', extension: javascript}, + 'python': {name: 'Python', extension: python}, + 'py': {name: 'Python', extension: python}, + 'rust': {name: 'Rust', extension: rust}, + 'rs': {name: 'Rust', extension: rust}, + 'go': {name: 'Go', extension: go}, + 'java': {name: 'Java', extension: java}, +}; + +export class CodeMirrorMobileEditor implements ICompilerExplorerEditor { + private view: EditorView; + private container: HTMLElement; + private languageCompartment: Compartment; + private lintCompartment: Compartment; + private currentLanguage: string; + private changeCallbacks: (() => void)[] = []; + private disposed = false; + private currentErrors: CompilerError[] = []; + + constructor(container: HTMLElement, options: EditorOptions = {}) { + this.container = container; + this.currentLanguage = options.language || 'cpp'; + + // Create compartments for dynamic reconfiguration + this.languageCompartment = new Compartment(); + this.lintCompartment = new Compartment(); + + // Create initial state + const initialState = EditorState.create({ + doc: '', + extensions: [ + basicSetup, + this.languageCompartment.of(this.getLanguageExtension(this.currentLanguage)), + this.lintCompartment.of([]), + EditorView.updateListener.of(this.createUpdateListener()), + EditorView.theme({ + '&': { + fontSize: options.fontSize ? `${options.fontSize}px` : '14px', + height: '100%', + }, + '.cm-content': { + padding: '10px', + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace', + minHeight: '200px', + }, + '.cm-focused': { + outline: 'none', + }, + '.cm-editor': { + height: '100%', + }, + '.cm-scroller': { + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace', + }, + // Error styling + '.cm-diagnostic-error': { + borderLeft: '3px solid #ff5555', + backgroundColor: 'rgba(255, 85, 85, 0.1)', + }, + '.cm-diagnostic-warning': { + borderLeft: '3px solid #ffaa00', + backgroundColor: 'rgba(255, 170, 0, 0.1)', + }, + '.cm-diagnostic-info': { + borderLeft: '3px solid #5555ff', + backgroundColor: 'rgba(85, 85, 255, 0.1)', + }, + }), + // Enable readonly if specified + ...(options.readOnly ? [EditorState.readOnly.of(true)] : []), + ], + }); + + // Create the editor view + this.view = new EditorView({ + state: initialState, + parent: container, + }); + + console.log('CodeMirrorMobileEditor created with language:', this.currentLanguage); + } + + private getLanguageExtension(languageId: string): any { + const langConfig = LANGUAGE_EXTENSIONS[languageId.toLowerCase()]; + if (langConfig) { + return langConfig.extension(); + } + // Default to C++ if language not found + return cpp(); + } + + private createUpdateListener() { + return (update: any) => { + if (update.docChanged) { + // Notify change callbacks + this.notifyChange(); + } + }; + } + + private notifyChange(): void { + if (this.disposed) return; + this.changeCallbacks.forEach(callback => { + try { + callback(); + } catch (error) { + console.error('Error in change callback:', error); + } + }); + } + + // ICompilerExplorerEditor implementation + getValue(): string { + if (this.disposed) throw new Error('Editor disposed'); + return this.view.state.doc.toString(); + } + + setValue(value: string): void { + if (this.disposed) throw new Error('Editor disposed'); + + const transaction = this.view.state.update({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: value, + }, + }); + + this.view.dispatch(transaction); + } + + setLanguage(language: string): void { + if (this.disposed) throw new Error('Editor disposed'); + + const normalizedLanguage = language.toLowerCase(); + if (this.currentLanguage === normalizedLanguage) return; + + this.currentLanguage = normalizedLanguage; + + // Update language extension + const languageExtension = this.getLanguageExtension(normalizedLanguage); + this.view.dispatch({ + effects: this.languageCompartment.reconfigure(languageExtension), + }); + + console.log('CodeMirror language changed to:', normalizedLanguage); + } + + setErrors(errors: CompilerError[]): void { + if (this.disposed) throw new Error('Editor disposed'); + + this.currentErrors = [...errors]; + + // Convert CompilerError[] to CodeMirror Diagnostic[] + const diagnostics: Diagnostic[] = errors.map(error => { + // Convert 1-based line numbers to 0-based positions + const line = Math.max(0, error.line - 1); + const doc = this.view.state.doc; + const lineInfo = doc.line(Math.min(line + 1, doc.lines)); + const from = lineInfo.from + Math.max(0, error.column - 1); + const to = Math.min(from + 1, lineInfo.to); // At least one character + + return { + from, + to, + severity: error.severity, + message: error.message, + }; + }); + + // Create linter that returns our diagnostics + const errorLinter = linter(() => diagnostics); + + // Update lint compartment + this.view.dispatch({ + effects: this.lintCompartment.reconfigure([ + errorLinter, + lintGutter(), + ]), + }); + } + + onChange(callback: () => void): void { + if (this.disposed) throw new Error('Editor disposed'); + this.changeCallbacks.push(callback); + } + + dispose(): void { + if (this.disposed) return; + + this.disposed = true; + this.changeCallbacks = []; + this.currentErrors = []; + + // Destroy CodeMirror view + this.view.destroy(); + + // Clear container + this.container.innerHTML = ''; + + console.log('CodeMirrorMobileEditor disposed'); + } + + // Additional methods for enhanced functionality + focus(): void { + if (!this.disposed) { + this.view.focus(); + } + } + + layout(): void { + // CodeMirror 6 handles layout automatically, but we can force a refresh + if (!this.disposed) { + this.view.requestMeasure(); + } + } + + getLanguage(): string { + return this.currentLanguage; + } + + getErrors(): CompilerError[] { + return [...this.currentErrors]; + } + + isDisposed(): boolean { + return this.disposed; + } + + // Get the underlying CodeMirror view for advanced usage + getCodeMirrorView(): EditorView | null { + return this.disposed ? null : this.view; + } +} \ No newline at end of file diff --git a/static/codemirror-test.ts b/static/codemirror-test.ts index 586f62e23..f67a442f1 100644 --- a/static/codemirror-test.ts +++ b/static/codemirror-test.ts @@ -1,18 +1,13 @@ // CodeMirror 6 test page entry point -import {EditorView} from '@codemirror/view'; -import {EditorState} from '@codemirror/state'; -import {syntaxHighlighting, HighlightStyle} from '@codemirror/language'; -import {tags} from '@lezer/highlight'; +import {EditorView, basicSetup} from 'codemirror'; import {cpp} from '@codemirror/lang-cpp'; +// import {createEditor, createEditorSync} from './editor-abstraction'; +import {CodeMirrorMobileEditor} from './codemirror-mobile-editor'; console.log('CodeMirror test script loaded, imports successful'); console.log('EditorView:', EditorView); -console.log('EditorState:', EditorState); -console.log('C++ language support:', cpp); -console.log('C++ language function result:', cpp()); -console.log('Syntax highlighting:', syntaxHighlighting); -console.log('Highlight tags:', tags); -console.log('C++ language support:', cpp); +console.log('basicSetup:', basicSetup); +console.log('cpp:', cpp); function initializeCodeMirrorTest() { console.log('CodeMirror 6 test page loaded - DOM ready'); @@ -35,141 +30,89 @@ int main() { const cmContainer = document.getElementById('codemirror-editor'); if (cmContainer) { try { - console.log('Creating CodeMirror editor...'); - - const cppExtension = cpp(); - console.log('C++ extension created:', cppExtension); - console.log('C++ extension type:', typeof cppExtension); - console.log('C++ extension constructor:', cppExtension.constructor?.name); - - // Create a custom highlight style to avoid version conflicts - const customHighlightStyle = HighlightStyle.define([ - { tag: tags.comment, color: '#008000' }, - { tag: tags.keyword, color: '#0000FF' }, - { tag: tags.string, color: '#800080' }, - { tag: tags.number, color: '#FF0000' }, - { tag: tags.variableName, color: '#000000' }, - { tag: tags.typeName, color: '#008080' }, - { tag: tags.operator, color: '#000000' }, - { tag: tags.bracket, color: '#000000' } - ]); - - console.log('Custom highlight style created:', customHighlightStyle); - - const highlightExtension = syntaxHighlighting(customHighlightStyle); - console.log('Highlight extension created:', highlightExtension); - - const extensions = [ - cppExtension, // Add C++ language support first - highlightExtension, // Add syntax highlighting - EditorView.lineWrapping, - EditorView.theme({ - '&': { height: '250px', border: '1px solid #ddd' }, - '.cm-content': { padding: '10px', fontFamily: 'monospace', fontSize: '14px' }, - '.cm-focused': { outline: 'none' } - }), - EditorView.updateListener.of(update => { - if (update.docChanged) { - console.log('CodeMirror content changed'); - const outputEl = document.getElementById('cm-output'); - if (outputEl) { - outputEl.textContent = - 'Content: ' + view.state.doc.toString().substring(0, 50) + '...'; - } - } - }) - ]; - - console.log('Extensions array:', extensions); - console.log('Extensions length:', extensions.length); - extensions.forEach((ext, i) => { - console.log(`Extension ${i}:`, ext, typeof ext); - }); - - const state = EditorState.create({ - doc: `// CodeMirror 6 Editor (real transaction-based) -#include + // Exact official example + new EditorView({ + parent: cmContainer, + doc: `#include int main() { - std::cout << "Hello from CodeMirror 6!" << std::endl; + std::cout << "Hello World!" << std::endl; return 0; }`, - extensions: extensions + extensions: [basicSetup, cpp()] }); - console.log('EditorState created:', state); - console.log('State language data:', state.languageDataAt('language', 0)); - - const view = new EditorView({ - state: state, - parent: cmContainer - }); - - console.log('EditorView created:', view); - - // Check if syntax highlighting is working - setTimeout(() => { - console.log('=== Checking syntax highlighting after render ==='); - const cmContent = cmContainer.querySelector('.cm-content'); - if (cmContent) { - console.log('CM content element found:', cmContent); - const highlightedElements = cmContent.querySelectorAll('[class*="cm-"]'); - console.log('Highlighted elements found:', highlightedElements.length); - Array.from(highlightedElements).forEach((el, i) => { - if (i < 5) { // Log first 5 elements - console.log(`Highlighted element ${i}:`, el.className, el.textContent); - } - }); - - // Check for specific C++ tokens - const includeElements = cmContent.querySelectorAll('[class*="keyword"], [class*="include"]'); - console.log('Include/keyword elements:', includeElements.length); - - const commentElements = cmContent.querySelectorAll('[class*="comment"]'); - console.log('Comment elements:', commentElements.length); - } else { - console.log('CM content element not found'); - } - }, 100); - - // Test the abstraction API - const setValueBtn = document.getElementById('test-setValue'); - if (setValueBtn) { - setValueBtn.onclick = () => { - view.dispatch({ - changes: { - from: 0, - to: view.state.doc.length, - insert: 'int main() { return 42; }' - } - }); - }; - } - - const getValueBtn = document.getElementById('test-getValue'); - if (getValueBtn) { - getValueBtn.onclick = () => { - const content = view.state.doc.toString(); - const outputEl = document.getElementById('cm-output'); - if (outputEl) { - outputEl.textContent = 'Content: ' + content; - } - }; - } - console.log('CodeMirror 6 editor created successfully'); - const statusEl = document.getElementById('cm-status'); - if (statusEl) { - statusEl.textContent = 'CodeMirror 6 loaded successfully with C++ syntax highlighting!'; - statusEl.style.color = 'green'; - } } catch (error) { console.error('Failed to create CodeMirror editor:', error); - const statusEl = document.getElementById('cm-status'); - if (statusEl) { - statusEl.textContent = 'CodeMirror 6 failed to load: ' + (error as Error).message; - statusEl.style.color = 'red'; - } + } + } + + // Test 3: CodeMirror Mobile Editor Class + const mobileContainer = document.getElementById('mobile-editor'); + if (mobileContainer) { + try { + console.log('Creating CodeMirrorMobileEditor...'); + + const mobileEditor = new CodeMirrorMobileEditor(mobileContainer, { + language: 'cpp', + fontSize: 14, + }); + + mobileEditor.setValue(`// CodeMirror Mobile Editor Test +#include +#include + +int main() { + std::vector numbers = {1, 2, 3, 4, 5}; + + for (const auto& num : numbers) { + std::cout << "Number: " << num << std::endl; + } + + return 0; +}`); + + // Test language switching + setTimeout(() => { + console.log('Testing language switch to JavaScript...'); + mobileEditor.setLanguage('javascript'); + mobileEditor.setValue(`// JavaScript test +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +console.log('Fibonacci(10):', fibonacci(10));`); + }, 2000); + + // Test error display + setTimeout(() => { + console.log('Testing error display...'); + mobileEditor.setErrors([ + { + line: 3, + column: 10, + message: 'Example compilation error', + severity: 'error' + }, + { + line: 8, + column: 5, + message: 'Example warning', + severity: 'warning' + } + ]); + }, 4000); + + // Test change callback + mobileEditor.onChange(() => { + console.log('Mobile editor content changed, new length:', mobileEditor.getValue().length); + }); + + console.log('CodeMirrorMobileEditor created successfully'); + } catch (error) { + console.error('Failed to create CodeMirrorMobileEditor:', error); } } } diff --git a/static/editor-abstraction.ts b/static/editor-abstraction.ts index bd38aa9bc..18176c492 100644 --- a/static/editor-abstraction.ts +++ b/static/editor-abstraction.ts @@ -94,8 +94,25 @@ export class MockEditor implements ICompilerExplorerEditor { } // Factory function for creating editors -export function createEditor(container: HTMLElement, options: EditorOptions): ICompilerExplorerEditor { - // For now, just return mock editor for testing - // Later this will detect mobile and return appropriate implementation +export async function createEditor(container: HTMLElement, options: EditorOptions): Promise { + // Detect mobile/touch devices + const isMobile = window.compilerExplorerOptions?.mobileViewer || + 'ontouchstart' in window || + navigator.maxTouchPoints > 0; + + if (isMobile) { + // Dynamic import for CodeMirror to avoid loading on desktop + const module = await import('./codemirror-mobile-editor'); + return new module.CodeMirrorMobileEditor(container, options); + } else { + // Return mock editor for desktop (later this will be Monaco wrapper) + return new MockEditor(); + } +} + +// Synchronous factory for immediate creation (uses mock for now) +export function createEditorSync(container: HTMLElement, options: EditorOptions): ICompilerExplorerEditor { + // For now, return mock editor for testing + // This will be replaced with proper sync creation once we integrate return new MockEditor(); } diff --git a/views/codemirror-test.pug b/views/codemirror-test.pug index 199d38f3a..d3b9cec69 100644 --- a/views/codemirror-test.pug +++ b/views/codemirror-test.pug @@ -20,6 +20,12 @@ block content h3 CodeMirror 6 Editor (Transaction-based) #codemirror-editor + .row.mt-4 + .col-12 + h3 CodeMirror Mobile Editor Class (with Language Support) + #mobile-editor + p.small This tests the full CodeMirrorMobileEditor class with language switching, error display, and change detection. + .row.mt-4 .col-12 h3 CodeMirror 6 Status