From 618a04387de76b65e0eb6da3c101b34ac89b0bad Mon Sep 17 00:00:00 2001 From: Parth Jadhao Date: Mon, 1 Jun 2026 17:47:09 +0530 Subject: [PATCH 1/2] fix: restored p5.js keyword highlighting for CM6 #3868 --- .../IDE/components/Editor/p5Highlight.js | 50 +++++++++++++++++++ .../IDE/components/Editor/stateUtils.js | 2 + client/styles/components/_editor.scss | 15 +++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 client/modules/IDE/components/Editor/p5Highlight.js diff --git a/client/modules/IDE/components/Editor/p5Highlight.js b/client/modules/IDE/components/Editor/p5Highlight.js new file mode 100644 index 0000000000..d454f18b51 --- /dev/null +++ b/client/modules/IDE/components/Editor/p5Highlight.js @@ -0,0 +1,50 @@ +import { ViewPlugin, Decoration } from '@codemirror/view'; +import { RangeSetBuilder } from '@codemirror/state'; +import { syntaxTree } from '@codemirror/language'; +import { + p5FunctionKeywords, + p5VariableKeywords +} from '../../../../utils/p5-keywords'; + +const p5Functions = new Set(Object.keys(p5FunctionKeywords)); +const p5Variables = new Set(Object.keys(p5VariableKeywords)); + +const p5FunctionMark = Decoration.mark({ class: 'cm-p5-function' }); +const p5VariableMark = Decoration.mark({ class: 'cm-p5-variable' }); + +function buildDecorations(view) { + const builder = new RangeSetBuilder(); + view.visibleRanges.forEach(({ from, to }) => { + syntaxTree(view.state).iterate({ + from, + to, + enter(node) { + const isVariable = node.name === 'VariableName'; + const isDefinition = node.name === 'VariableDefinition'; + if (!isVariable && !isDefinition) return; + const name = view.state.doc.sliceString(node.from, node.to); + if (p5Functions.has(name)) { + builder.add(node.from, node.to, p5FunctionMark); + } else if (p5Variables.has(name)) { + builder.add(node.from, node.to, p5VariableMark); + } + } + }); + }); + return builder.finish(); +} + +export const p5Highlight = ViewPlugin.fromClass( + class { + constructor(view) { + this.decorations = buildDecorations(view); + } + + update(update) { + if (update.docChanged || update.viewportChanged) { + this.decorations = buildDecorations(update.view); + } + } + }, + { decorations: (v) => v.decorations } +); diff --git a/client/modules/IDE/components/Editor/stateUtils.js b/client/modules/IDE/components/Editor/stateUtils.js index c526f1b414..2bc1418c67 100644 --- a/client/modules/IDE/components/Editor/stateUtils.js +++ b/client/modules/IDE/components/Editor/stateUtils.js @@ -59,6 +59,7 @@ import { Linter as ESLinter } from 'eslint-linter-browserify'; import { tidyCodeWithPrettier } from './tidier'; import p5JavaScript from './p5JavaScript'; import { highlightStyle } from './highlightStyle'; +import { p5Highlight } from './p5Highlight'; import { errorDecorationStateField } from './consoleErrorDecoration'; // ----- TODOS ----- @@ -446,6 +447,7 @@ export function createNewFileState(filename, document, settings) { highlightSpecialChars(), highlightSelectionMatches(), syntaxHighlighting(highlightStyle), + p5Highlight, // Selection extensions drawSelection(), rectangularSelection(), diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 93866036cd..145ceb1762 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -306,7 +306,20 @@ // contentSeparator, monospace, strikethrough, inserted, deleted, changed, invalid, meta, // documentMeta, annotation, processingInstruction, standard, special, macroName -// TODO(connie): Add p5 specific highlighting styles, like .cm-p5-function, .cm-p5-variable +.cm-p5-function, +.cm-p5-function .cm-variable { + @include themify() { + color: getThemifyVariable('highlight-style-variable-color'); + font-weight: bold; + } +} + +.cm-p5-variable, +.cm-p5-variable .cm-variable { + @include themify() { + color: getThemifyVariable('highlight-style-atom-color'); + } +} // Additional matching selection styling .cm-matchingBracket { From 76cef81aceab400f96c3e7de9c3317bc0d815a6e Mon Sep 17 00:00:00 2001 From: Parth Jadhao Date: Wed, 3 Jun 2026 01:04:02 +0530 Subject: [PATCH 2/2] refactor: move p5 highlight logic into p5JavaScript.js --- .../IDE/components/Editor/p5Highlight.js | 50 ----------------- .../IDE/components/Editor/p5JavaScript.js | 54 ++++++++++++++++++- .../IDE/components/Editor/stateUtils.js | 2 - client/styles/components/_editor.scss | 2 + 4 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 client/modules/IDE/components/Editor/p5Highlight.js diff --git a/client/modules/IDE/components/Editor/p5Highlight.js b/client/modules/IDE/components/Editor/p5Highlight.js deleted file mode 100644 index d454f18b51..0000000000 --- a/client/modules/IDE/components/Editor/p5Highlight.js +++ /dev/null @@ -1,50 +0,0 @@ -import { ViewPlugin, Decoration } from '@codemirror/view'; -import { RangeSetBuilder } from '@codemirror/state'; -import { syntaxTree } from '@codemirror/language'; -import { - p5FunctionKeywords, - p5VariableKeywords -} from '../../../../utils/p5-keywords'; - -const p5Functions = new Set(Object.keys(p5FunctionKeywords)); -const p5Variables = new Set(Object.keys(p5VariableKeywords)); - -const p5FunctionMark = Decoration.mark({ class: 'cm-p5-function' }); -const p5VariableMark = Decoration.mark({ class: 'cm-p5-variable' }); - -function buildDecorations(view) { - const builder = new RangeSetBuilder(); - view.visibleRanges.forEach(({ from, to }) => { - syntaxTree(view.state).iterate({ - from, - to, - enter(node) { - const isVariable = node.name === 'VariableName'; - const isDefinition = node.name === 'VariableDefinition'; - if (!isVariable && !isDefinition) return; - const name = view.state.doc.sliceString(node.from, node.to); - if (p5Functions.has(name)) { - builder.add(node.from, node.to, p5FunctionMark); - } else if (p5Variables.has(name)) { - builder.add(node.from, node.to, p5VariableMark); - } - } - }); - }); - return builder.finish(); -} - -export const p5Highlight = ViewPlugin.fromClass( - class { - constructor(view) { - this.decorations = buildDecorations(view); - } - - update(update) { - if (update.docChanged || update.viewportChanged) { - this.decorations = buildDecorations(update.view); - } - } - }, - { decorations: (v) => v.decorations } -); diff --git a/client/modules/IDE/components/Editor/p5JavaScript.js b/client/modules/IDE/components/Editor/p5JavaScript.js index ebafb81eca..f3d0f2af5a 100644 --- a/client/modules/IDE/components/Editor/p5JavaScript.js +++ b/client/modules/IDE/components/Editor/p5JavaScript.js @@ -1,8 +1,57 @@ -import { LanguageSupport } from '@codemirror/language'; +import { LanguageSupport, syntaxTree } from '@codemirror/language'; import { javascript } from '@codemirror/lang-javascript'; +import { ViewPlugin, Decoration } from '@codemirror/view'; +import { RangeSetBuilder } from '@codemirror/state'; import { p5Hinter } from '../../../../utils/p5-hinter'; import { p5CompletionPreview } from './p5CompletionPreview'; import contextAwareHinter from '../../../../utils/contextAwareHinter'; +import { + p5FunctionKeywords, + p5VariableKeywords +} from '../../../../utils/p5-keywords'; + +const p5Functions = new Set(Object.keys(p5FunctionKeywords)); +const p5Variables = new Set(Object.keys(p5VariableKeywords)); + +const p5FunctionMark = Decoration.mark({ class: 'cm-p5-function' }); +const p5VariableMark = Decoration.mark({ class: 'cm-p5-variable' }); + +function buildDecorations(view) { + const builder = new RangeSetBuilder(); + view.visibleRanges.forEach(({ from, to }) => { + syntaxTree(view.state).iterate({ + from, + to, + enter(node) { + const isVariable = node.name === 'VariableName'; + const isDefinition = node.name === 'VariableDefinition'; + if (!isVariable && !isDefinition) return; + const name = view.state.doc.sliceString(node.from, node.to); + if (p5Functions.has(name)) { + builder.add(node.from, node.to, p5FunctionMark); + } else if (p5Variables.has(name)) { + builder.add(node.from, node.to, p5VariableMark); + } + } + }); + }); + return builder.finish(); +} + +const p5Highlight = ViewPlugin.fromClass( + class { + constructor(view) { + this.decorations = buildDecorations(view); + } + + update(update) { + if (update.docChanged || update.viewportChanged) { + this.decorations = buildDecorations(update.view); + } + } + }, + { decorations: (v) => v.decorations } +); function addCompletions(context) { const word = context.matchBefore(/\w*/); @@ -23,6 +72,7 @@ export default function p5JavaScript() { jsLang.language.data.of({ autocomplete: addCompletions }), - p5CompletionPreview() + p5CompletionPreview(), + p5Highlight ]); } diff --git a/client/modules/IDE/components/Editor/stateUtils.js b/client/modules/IDE/components/Editor/stateUtils.js index 2bc1418c67..c526f1b414 100644 --- a/client/modules/IDE/components/Editor/stateUtils.js +++ b/client/modules/IDE/components/Editor/stateUtils.js @@ -59,7 +59,6 @@ import { Linter as ESLinter } from 'eslint-linter-browserify'; import { tidyCodeWithPrettier } from './tidier'; import p5JavaScript from './p5JavaScript'; import { highlightStyle } from './highlightStyle'; -import { p5Highlight } from './p5Highlight'; import { errorDecorationStateField } from './consoleErrorDecoration'; // ----- TODOS ----- @@ -447,7 +446,6 @@ export function createNewFileState(filename, document, settings) { highlightSpecialChars(), highlightSelectionMatches(), syntaxHighlighting(highlightStyle), - p5Highlight, // Selection extensions drawSelection(), rectangularSelection(), diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 145ceb1762..72051b096a 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -306,6 +306,8 @@ // contentSeparator, monospace, strikethrough, inserted, deleted, changed, invalid, meta, // documentMeta, annotation, processingInstruction, standard, special, macroName +// CM6 uses EditorView.theme() instead of cm-s-* classes, so the p5 highlight styles +// from the CM5 theme files do not apply. These rules replicate them for CM6. .cm-p5-function, .cm-p5-function .cm-variable { @include themify() {