diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts
index 5bc7d05ee2..674d91af18 100644
--- a/cypress/e2e/6-code-node.cy.ts
+++ b/cypress/e2e/6-code-node.cy.ts
@@ -57,7 +57,7 @@ for (const item of $input.all()) {
return
`);
- getParameter().get('.cm-lint-marker-error').should('have.length', 6);
+ getParameter().get('.cm-lintRange-error').should('have.length', 6);
getParameter().contains('itemMatching').realHover();
cy.get('.cm-tooltip-lint').should(
'have.text',
@@ -81,7 +81,7 @@ $input.item()
return []
`);
- getParameter().get('.cm-lint-marker-error').should('have.length', 5);
+ getParameter().get('.cm-lintRange-error').should('have.length', 5);
getParameter().contains('all').realHover();
cy.get('.cm-tooltip-lint').should(
'have.text',
diff --git a/packages/design-system/src/css/_primitives.scss b/packages/design-system/src/css/_primitives.scss
index 71c6b4c10f..73d47a7fd4 100644
--- a/packages/design-system/src/css/_primitives.scss
+++ b/packages/design-system/src/css/_primitives.scss
@@ -16,6 +16,7 @@
--prim-gray-490: hsl(var(--prim-gray-h), 3%, 51%);
--prim-gray-420: hsl(var(--prim-gray-h), 4%, 58%);
--prim-gray-320: hsl(var(--prim-gray-h), 10%, 68%);
+ --prim-gray-320-alpha-010: hsla(var(--prim-gray-h), 10%, 68%, 0.1);
--prim-gray-200: hsl(var(--prim-gray-h), 18%, 80%);
--prim-gray-120: hsl(var(--prim-gray-h), 25%, 88%);
--prim-gray-70: hsl(var(--prim-gray-h), 32%, 93%);
diff --git a/packages/design-system/src/css/_tokens.dark.scss b/packages/design-system/src/css/_tokens.dark.scss
index 4ea6566677..00eeafb981 100644
--- a/packages/design-system/src/css/_tokens.dark.scss
+++ b/packages/design-system/src/css/_tokens.dark.scss
@@ -139,12 +139,20 @@
--color-infobox-examples-border-color: var(--prim-gray-670);
// Code
- --color-code-tags-string: var(--prim-color-alt-f-tint-150);
- --color-code-tags-primitive: var(--prim-color-alt-b-shade-100);
- --color-code-tags-keyword: var(--prim-color-alt-g-tint-150);
- --color-code-tags-operator: var(--prim-color-alt-h);
- --color-code-tags-variable: var(--prim-color-primary-tint-100);
- --color-code-tags-definition: var(--prim-color-alt-e);
+ --color-code-tags-string: #9ecbff;
+ --color-code-tags-regex: #9ecbff;
+ --color-code-tags-primitive: #79b8ff;
+ --color-code-tags-keyword: #f97583;
+ --color-code-tags-variable: #79b8ff;
+ --color-code-tags-parameter: #e1e4e8;
+ --color-code-tags-function: #b392f0;
+ --color-code-tags-constant: #79b8ff;
+ --color-code-tags-property: #79b8ff;
+ --color-code-tags-type: #b392f0;
+ --color-code-tags-class: #b392f0;
+ --color-code-tags-heading: #79b8ff;
+ --color-code-tags-invalid: #f97583;
+ --color-code-tags-comment: #6a737d;
--color-json-default: var(--prim-color-secondary-tint-200);
--color-json-null: var(--color-danger);
--color-json-boolean: var(--prim-color-alt-a);
@@ -155,15 +163,18 @@
--color-json-brackets-hover: var(--prim-color-alt-e);
--color-json-line: var(--prim-gray-200);
--color-json-highlight: var(--color-background-base);
- --color-code-background: var(--prim-gray-800);
+ --color-code-background: var(--prim-gray-820);
--color-code-background-readonly: var(--prim-gray-740);
- --color-code-lineHighlight: var(--prim-gray-740);
+ --color-code-lineHighlight: var(--prim-gray-320-alpha-010);
--color-code-foreground: var(--prim-gray-70);
--color-code-caret: var(--prim-gray-10);
- --color-code-selection: var(--prim-color-alt-e-alpha-04);
- --color-code-gutterBackground: var(--prim-gray-670);
- --color-code-gutterForeground: var(--prim-gray-320);
- --color-code-tags-comment: var(--prim-gray-200);
+ --color-code-selection: #3392ff44;
+ --color-code-selection-highlight: #17e5e633;
+ --color-code-gutter-background: var(--prim-gray-820);
+ --color-code-gutter-foreground: var(--prim-gray-320);
+ --color-code-gutter-foreground-active: var(--prim-gray-10);
+ --color-code-indentation-marker: var(--prim-gray-740);
+ --color-code-indentation-marker-active: var(--prim-gray-670);
--color-line-break: var(--prim-gray-420);
--color-code-line-break: var(--prim-color-secondary-tint-100);
diff --git a/packages/design-system/src/css/_tokens.scss b/packages/design-system/src/css/_tokens.scss
index e4faf088cf..3b6b7451be 100644
--- a/packages/design-system/src/css/_tokens.scss
+++ b/packages/design-system/src/css/_tokens.scss
@@ -183,12 +183,20 @@
--color-infobox-examples-border-color: var(--color-foreground-light);
// Code
- --color-code-tags-string: var(--prim-color-alt-f);
- --color-code-tags-primitive: var(--prim-color-alt-b-shade-100);
- --color-code-tags-keyword: var(--prim-color-alt-g);
- --color-code-tags-operator: var(--prim-color-alt-h);
- --color-code-tags-variable: var(--prim-color-alt-c-shade-100);
- --color-code-tags-definition: var(--prim-color-alt-e-shade-150);
+ --color-code-tags-string: #032f62;
+ --color-code-tags-regex: #032f62;
+ --color-code-tags-primitive: #005cc5;
+ --color-code-tags-keyword: #d73a49;
+ --color-code-tags-variable: #005cc5;
+ --color-code-tags-parameter: #24292e;
+ --color-code-tags-function: #6f42c1;
+ --color-code-tags-constant: #005cc5;
+ --color-code-tags-property: #005cc5;
+ --color-code-tags-type: #005cc5;
+ --color-code-tags-class: #6f42c1;
+ --color-code-tags-heading: #005cc5;
+ --color-code-tags-invalid: #cb2431;
+ --color-code-tags-comment: #6a737d;
--color-json-default: var(--prim-color-secondary-shade-100);
--color-json-null: var(--prim-color-alt-c);
--color-json-boolean: var(--prim-color-alt-a);
@@ -201,13 +209,16 @@
--color-json-highlight: var(--prim-gray-70);
--color-code-background: var(--prim-gray-0);
--color-code-background-readonly: var(--prim-gray-40);
- --color-code-lineHighlight: var(--prim-gray-40);
+ --color-code-lineHighlight: var(--prim-gray-320-alpha-010);
--color-code-foreground: var(--prim-gray-670);
- --color-code-caret: var(--prim-gray-420);
- --color-code-selection: var(--prim-gray-120);
- --color-code-gutterBackground: var(--prim-gray-0);
- --color-code-gutterForeground: var(--prim-gray-320);
- --color-code-tags-comment: var(--prim-gray-420);
+ --color-code-caret: var(--prim-gray-820);
+ --color-code-selection: #0366d625;
+ --color-code-selection-highlight: #34d05840;
+ --color-code-gutter-background: var(--prim-gray-0);
+ --color-code-gutter-foreground: var(--prim-gray-320);
+ --color-code-gutter-foreground-active: var(--prim-gray-670);
+ --color-code-indentation-marker: var(--prim-gray-70);
+ --color-code-indentation-marker-active: var(--prim-gray-200);
--color-line-break: var(--prim-gray-320);
--color-code-line-break: var(--prim-color-secondary-tint-200);
diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json
index 3293abfe3b..e7756983a7 100644
--- a/packages/editor-ui/package.json
+++ b/packages/editor-ui/package.json
@@ -25,6 +25,7 @@
"@codemirror/lang-python": "^6.1.6",
"@codemirror/language": "^6.10.1",
"@codemirror/lint": "^6.8.0",
+ "@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.26.3",
"@fontsource/open-sans": "^4.5.0",
@@ -39,6 +40,8 @@
"@n8n/codemirror-lang": "workspace:*",
"@n8n/codemirror-lang-sql": "^1.0.2",
"@n8n/permissions": "workspace:*",
+ "@replit/codemirror-indentation-markers": "^6.5.3",
+ "@typescript/vfs": "^1.6.0",
"@sentry/vue": "catalog:frontend",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.2",
@@ -52,6 +55,7 @@
"change-case": "^5.4.4",
"chart.js": "^4.4.0",
"codemirror-lang-html-n8n": "^1.0.0",
+ "comlink": "^4.4.1",
"dateformat": "^3.0.3",
"email-providers": "^2.0.1",
"esprima-next": "5.8.4",
@@ -70,6 +74,7 @@
"qrcode.vue": "^3.3.4",
"stream-browserify": "^3.0.0",
"timeago.js": "^4.0.2",
+ "typescript": "^5.5.2",
"uuid": "catalog:",
"v3-infinite-loading": "^1.2.2",
"vue": "catalog:frontend",
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
index 3f74bfe7a0..c17ff804c0 100644
--- a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
+++ b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
@@ -1,32 +1,24 @@
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts b/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts
deleted file mode 100644
index a9b00a0b82..0000000000
--- a/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import {
- dropCursor,
- EditorView,
- highlightActiveLine,
- highlightActiveLineGutter,
- highlightSpecialChars,
- keymap,
- lineNumbers,
-} from '@codemirror/view';
-import { bracketMatching, foldGutter, indentOnInput } from '@codemirror/language';
-import { history, toggleComment, deleteCharBackward } from '@codemirror/commands';
-import { lintGutter } from '@codemirror/lint';
-import { type Extension, Prec } from '@codemirror/state';
-
-import { codeInputHandler } from '@/plugins/codemirror/inputHandlers/code.inputHandler';
-import {
- autocompleteKeyMap,
- enterKeyMap,
- historyKeyMap,
- tabKeyMap,
-} from '@/plugins/codemirror/keymap';
-
-export const readOnlyEditorExtensions: readonly Extension[] = [
- lineNumbers(),
- EditorView.lineWrapping,
- highlightSpecialChars(),
-];
-
-export const writableEditorExtensions: readonly Extension[] = [
- history(),
- lintGutter(),
- foldGutter(),
- codeInputHandler(),
- dropCursor(),
- indentOnInput(),
- bracketMatching(),
- highlightActiveLine(),
- highlightActiveLineGutter(),
- Prec.highest(
- keymap.of([
- ...tabKeyMap(),
- ...enterKeyMap,
- ...autocompleteKeyMap,
- ...historyKeyMap,
- { key: 'Mod-/', run: toggleComment },
- { key: 'Backspace', run: deleteCharBackward, shift: deleteCharBackward },
- ]),
- ),
-];
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/constants.ts b/packages/editor-ui/src/components/CodeNodeEditor/constants.ts
index d9dcaccabd..2f648e41d2 100644
--- a/packages/editor-ui/src/components/CodeNodeEditor/constants.ts
+++ b/packages/editor-ui/src/components/CodeNodeEditor/constants.ts
@@ -28,7 +28,7 @@ export const AUTOCOMPLETABLE_BUILT_IN_MODULES_JS = [
export const DEFAULT_LINTER_SEVERITY: Diagnostic['severity'] = 'error';
-export const DEFAULT_LINTER_DELAY_IN_MS = 300;
+export const DEFAULT_LINTER_DELAY_IN_MS = 500;
/**
* Length of the start of the script wrapper, used as offset for the linter to find a location in source text.
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts
index defbdbfa73..ff954d5bc7 100644
--- a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts
+++ b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts
@@ -1,10 +1,10 @@
import type { Diagnostic } from '@codemirror/lint';
-import { linter } from '@codemirror/lint';
+import { linter as codeMirrorLinter } from '@codemirror/lint';
import type { EditorView } from '@codemirror/view';
import * as esprima from 'esprima-next';
import type { Node, MemberExpression } from 'estree';
import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
-import { toValue, type MaybeRefOrGetter } from 'vue';
+import { computed, toValue, type MaybeRefOrGetter } from 'vue';
import { useI18n } from '@/composables/useI18n';
import {
@@ -17,17 +17,17 @@ import { walk } from './utils';
export const useLinter = (
mode: MaybeRefOrGetter,
- editor: MaybeRefOrGetter,
+ language: MaybeRefOrGetter,
) => {
const i18n = useI18n();
-
- function createLinter(language: CodeNodeEditorLanguage) {
- switch (language) {
+ const linter = computed(() => {
+ switch (toValue(language)) {
case 'javaScript':
- return linter(lintSource, { delay: DEFAULT_LINTER_DELAY_IN_MS });
+ return codeMirrorLinter(lintSource, { delay: DEFAULT_LINTER_DELAY_IN_MS });
}
- return undefined;
- }
+
+ return [];
+ });
function lintSource(editorView: EditorView): Diagnostic[] {
const doc = editorView.state.doc.toString();
@@ -38,34 +38,7 @@ export const useLinter = (
try {
ast = esprima.parseScript(script, { range: true });
} catch (syntaxError) {
- let line;
-
- try {
- const lineAtError = editorView.state.doc.line(syntaxError.lineNumber - 1).text;
-
- // optional chaining operators currently unsupported by esprima-next
- if (['?.', ']?'].some((operator) => lineAtError.includes(operator))) return [];
- } catch {
- return [];
- }
-
- try {
- line = editorView.state.doc.line(syntaxError.lineNumber);
-
- return [
- {
- from: line.from,
- to: line.to,
- severity: DEFAULT_LINTER_SEVERITY,
- message: i18n.baseText('codeNodeEditor.linter.bothModes.syntaxError'),
- },
- ];
- } catch {
- /**
- * For invalid (e.g. half-written) n8n syntax, esprima errors with an off-by-one line number for the final line. In future, we should add full linting for n8n syntax before parsing JS.
- */
- return [];
- }
+ return [];
}
if (ast === null) return [];
@@ -118,7 +91,7 @@ export const useLinter = (
walk(ast, isUnavailableVarInAllItems).forEach((node) => {
const [start, end] = getRange(node);
- const varName = getText(node);
+ const varName = getText(editorView, node);
if (!varName) return;
@@ -250,7 +223,7 @@ export const useLinter = (
walk(ast, isUnavailableMethodinEachItem).forEach((node) => {
const [start, end] = getRange(node.property);
- const method = getText(node.property);
+ const method = getText(editorView, node.property);
if (!method) return;
@@ -444,7 +417,7 @@ export const useLinter = (
if (shadowStart && start > shadowStart) return; // skip shadow item
- const varName = getText(node);
+ const varName = getText(editorView, node);
if (!varName) return;
@@ -489,7 +462,7 @@ export const useLinter = (
!['json', 'binary'].includes(node.property.name);
walk(ast, isDirectAccessToItemSubproperty).forEach((node) => {
- const varName = getText(node);
+ const varName = getText(editorView, node);
if (!varName) return;
@@ -636,19 +609,15 @@ export const useLinter = (
// helpers
// ----------------------------------
- function getText(node: RangeNode) {
- const editorValue = toValue(editor);
-
- if (!editorValue) return null;
-
+ function getText(editorView: EditorView, node: RangeNode) {
const [start, end] = getRange(node);
- return editorValue.state.doc.toString().slice(start, end);
+ return editorView.state.doc.toString().slice(start, end);
}
function getRange(node: RangeNode) {
return node.range.map((loc) => loc - OFFSET_FOR_SCRIPT_WRAPPER);
}
- return { createLinter };
+ return linter;
};
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/theme.ts b/packages/editor-ui/src/components/CodeNodeEditor/theme.ts
index e8e10aaad8..aa5977bc3e 100644
--- a/packages/editor-ui/src/components/CodeNodeEditor/theme.ts
+++ b/packages/editor-ui/src/components/CodeNodeEditor/theme.ts
@@ -32,16 +32,62 @@ interface ThemeSettings {
maxHeight?: string;
minHeight?: string;
rows?: number;
- highlightColors?: 'default' | 'html';
}
-export const codeNodeEditorTheme = ({
- isReadOnly,
- minHeight,
- maxHeight,
- rows,
- highlightColors,
-}: ThemeSettings) => [
+const codeEditorSyntaxHighlighting = syntaxHighlighting(
+ HighlightStyle.define([
+ { tag: tags.keyword, color: 'var(--color-code-tags-keyword)' },
+ {
+ tag: [
+ tags.deleted,
+ tags.character,
+ tags.macroName,
+ tags.definition(tags.name),
+ tags.definition(tags.variableName),
+ tags.atom,
+ tags.bool,
+ ],
+ color: 'var(--color-code-tags-variable)',
+ },
+ { tag: [tags.name, tags.propertyName], color: 'var(--color-code-tags-property)' },
+ {
+ tag: [tags.processingInstruction, tags.string, tags.inserted, tags.special(tags.string)],
+ color: 'var(--color-code-tags-string)',
+ },
+ {
+ tag: [tags.function(tags.variableName), tags.labelName],
+ color: 'var(--color-code-tags-function)',
+ },
+ {
+ tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
+ color: 'var(--color-code-tags-constant)',
+ },
+ { tag: [tags.className], color: 'var(--color-code-tags-class)' },
+ {
+ tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace],
+ color: 'var(--color-code-tags-primitive)',
+ },
+ { tag: [tags.typeName], color: 'var(--color-code-tags-type)' },
+ { tag: [tags.operator, tags.operatorKeyword], color: 'var(--color-code-tags-keyword)' },
+ {
+ tag: [tags.url, tags.escape, tags.regexp, tags.link],
+ color: 'var(--color-code-tags-keyword)',
+ },
+ { tag: [tags.meta, tags.comment, tags.lineComment], color: 'var(--color-code-tags-comment)' },
+ { tag: tags.strong, fontWeight: 'bold' },
+ { tag: tags.emphasis, fontStyle: 'italic' },
+ { tag: tags.link, textDecoration: 'underline' },
+ { tag: tags.heading, fontWeight: 'bold', color: 'var(--color-code-tags-heading)' },
+ { tag: tags.invalid, color: 'var(--color-code-tags-invalid)' },
+ { tag: tags.strikethrough, textDecoration: 'line-through' },
+ {
+ tag: [tags.derefOperator, tags.special(tags.variableName), tags.variableName, tags.separator],
+ color: 'var(--color-code-foreground)',
+ },
+ ]),
+);
+
+export const codeEditorTheme = ({ isReadOnly, minHeight, maxHeight, rows }: ThemeSettings) => [
EditorView.theme({
'&': {
'font-size': BASE_STYLING.fontSize,
@@ -54,13 +100,17 @@ export const codeNodeEditorTheme = ({
'.cm-content': {
fontFamily: BASE_STYLING.fontFamily,
caretColor: isReadOnly ? 'transparent' : 'var(--color-code-caret)',
+ lineHeight: 'var(--font-line-height-xloose)',
+ paddingTop: 'var(--spacing-2xs)',
+ paddingBottom: 'var(--spacing-s)',
},
'.cm-cursor, .cm-dropCursor': {
borderLeftColor: 'var(--color-code-caret)',
},
- '&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': {
- backgroundColor: 'var(--color-code-selection)',
- },
+ '&.cm-focused > .cm-scroller .cm-selectionLayer > .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
+ {
+ background: 'var(--color-code-selection)',
+ },
'&.cm-editor': {
...(isReadOnly ? { backgroundColor: 'var(--color-code-background-readonly)' } : {}),
borderColor: 'var(--border-color-base)',
@@ -75,13 +125,19 @@ export const codeNodeEditorTheme = ({
'.cm-activeLineGutter': {
backgroundColor: 'var(--color-code-lineHighlight)',
},
+ '.cm-lineNumbers .cm-activeLineGutter': {
+ color: 'var(--color-code-gutter-foreground-active)',
+ },
'.cm-gutters': {
backgroundColor: isReadOnly
? 'var(--color-code-background-readonly)'
- : 'var(--color-code-gutterBackground)',
- color: 'var(--color-code-gutterForeground)',
+ : 'var(--color-code-gutter-background)',
+ color: 'var(--color-code-gutter-foreground)',
+ border: '0',
borderRadius: 'var(--border-radius-base)',
- borderRightColor: 'var(--border-color-base)',
+ },
+ '.cm-gutterElement': {
+ padding: 0,
},
'.cm-tooltip': {
maxWidth: BASE_STYLING.tooltip.maxWidth,
@@ -92,11 +148,30 @@ export const codeNodeEditorTheme = ({
maxHeight: maxHeight ?? '100%',
...(isReadOnly
? {}
- : { minHeight: rows && rows !== -1 ? `${Number(rows + 1) * 1.3}em` : 'auto' }),
+ : {
+ minHeight: rows && rows !== -1 ? `${Number(rows + 1) * 1.3}em` : 'auto',
+ }),
+ },
+ '.cm-lineNumbers .cm-gutterElement': {
+ padding: '0 var(--spacing-5xs) 0 var(--spacing-2xs)',
},
'.cm-gutter,.cm-content': {
minHeight: rows && rows !== -1 ? 'auto' : (minHeight ?? 'calc(35vh - var(--spacing-2xl))'),
},
+ '.cm-foldGutter': {
+ width: '16px',
+ },
+ '.cm-fold-marker': {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ opacity: 0,
+ transition: 'opacity 0.3s ease',
+ },
+ '.cm-activeLineGutter .cm-fold-marker, .cm-gutters:hover .cm-fold-marker': {
+ opacity: 1,
+ },
'.cm-diagnosticAction': {
backgroundColor: BASE_STYLING.diagnosticButton.backgroundColor,
color: 'var(--color-primary)',
@@ -106,103 +181,81 @@ export const codeNodeEditorTheme = ({
cursor: BASE_STYLING.diagnosticButton.cursor,
},
'.cm-diagnostic-error': {
- backgroundColor: 'var(--color-background-base)',
+ backgroundColor: 'var(--color-infobox-background)',
},
'.cm-diagnosticText': {
+ fontSize: 'var(--font-size-xs)',
color: 'var(--color-text-base)',
},
+ '.cm-diagnosticDocs': {
+ fontSize: 'var(--font-size-2xs)',
+ },
+ '.cm-foldPlaceholder': {
+ color: 'var(--color-text-base)',
+ backgroundColor: 'var(--color-background-base)',
+ border: 'var(--border-base)',
+ },
+ '.cm-selectionMatch': {
+ background: 'var(--color-code-selection-highlight)',
+ },
+ '.cm-selectionMatch-main': {
+ background: 'var(--color-code-selection-highlight)',
+ },
+ '.cm-matchingBracket': {
+ background: 'var(--color-code-selection)',
+ },
+ '.cm-completionMatchedText': {
+ textDecoration: 'none',
+ fontWeight: '600',
+ color: 'var(--color-autocomplete-item-selected)',
+ },
+ '.cm-faded > span': {
+ opacity: 0.6,
+ },
+ '.cm-panel.cm-search': {
+ padding: 'var(--spacing-4xs) var(--spacing-2xs)',
+ },
+ '.cm-panels': {
+ background: 'var(--color-background-light)',
+ color: 'var(--color-text-base)',
+ },
+ '.cm-panels-bottom': {
+ borderTop: 'var(--border-base)',
+ },
+ '.cm-textfield': {
+ color: 'var(--color-text-dark)',
+ background: 'var(--color-foreground-xlight)',
+ borderRadius: 'var(--border-radius-base)',
+ border: 'var(--border-base)',
+ fontSize: '90%',
+ },
+ '.cm-textfield:focus': {
+ outline: 'none',
+ borderColor: 'var(--color-secondary)',
+ },
+ '.cm-panel button': {
+ color: 'var(--color-text-base)',
+ },
+ '.cm-panel input[type="checkbox"]': {
+ border: 'var(--border-base)',
+ outline: 'none',
+ },
+ '.cm-panel input[type="checkbox"]:hover': {
+ border: 'var(--border-base)',
+ outline: 'none',
+ },
+ '.cm-panel.cm-search label': {
+ fontSize: '90%',
+ },
+ '.cm-button': {
+ outline: 'none',
+ border: 'var(--border-base)',
+ color: 'var(--color-text-dark)',
+ backgroundColor: 'var(--color-foreground-xlight)',
+ backgroundImage: 'none',
+ borderRadius: 'var(--border-radius-base)',
+ fontSize: '90%',
+ },
}),
- highlightColors === 'html'
- ? syntaxHighlighting(
- HighlightStyle.define([
- { tag: tags.keyword, color: '#c678dd' },
- {
- tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
- color: '#e06c75',
- },
- { tag: [tags.function(tags.variableName), tags.labelName], color: '#61afef' },
- {
- tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
- color: '#d19a66',
- },
- { tag: [tags.definition(tags.name), tags.separator], color: '#abb2bf' },
- {
- tag: [
- tags.typeName,
- tags.className,
- tags.number,
- tags.changed,
- tags.annotation,
- tags.modifier,
- tags.self,
- tags.namespace,
- ],
- color: '#e06c75',
- },
- {
- tag: [
- tags.operator,
- tags.operatorKeyword,
- tags.url,
- tags.escape,
- tags.regexp,
- tags.link,
- tags.special(tags.string),
- ],
- color: '#56b6c2',
- },
- { tag: [tags.meta, tags.comment], color: '#7d8799' },
- { tag: tags.strong, fontWeight: 'bold' },
- { tag: tags.emphasis, fontStyle: 'italic' },
- { tag: tags.strikethrough, textDecoration: 'line-through' },
- { tag: tags.link, color: '#7d8799', textDecoration: 'underline' },
- { tag: tags.heading, fontWeight: 'bold', color: '#e06c75' },
- { tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: '#d19a66' },
- { tag: [tags.processingInstruction, tags.string, tags.inserted], color: '#98c379' },
- { tag: tags.invalid, color: 'red', 'font-weight': 'bold' },
- ]),
- )
- : syntaxHighlighting(
- HighlightStyle.define([
- {
- tag: tags.comment,
- color: 'var(--color-code-tags-comment)',
- },
- {
- tag: [tags.string, tags.special(tags.brace)],
- color: 'var(--color-code-tags-string)',
- },
- {
- tag: [tags.number, tags.self, tags.bool, tags.null],
- color: 'var(--color-code-tags-primitive)',
- },
- {
- tag: tags.keyword,
- color: 'var(--color-code-tags-keyword)',
- },
- {
- tag: tags.operator,
- color: 'var(--color-code-tags-operator)',
- },
- {
- tag: [
- tags.variableName,
- tags.propertyName,
- tags.attributeName,
- tags.regexp,
- tags.className,
- tags.typeName,
- ],
- color: 'var(--color-code-tags-variable)',
- },
- {
- tag: [
- tags.definition(tags.typeName),
- tags.definition(tags.propertyName),
- tags.function(tags.variableName),
- ],
- color: 'var(--color-code-tags-definition)',
- },
- ]),
- ),
+ codeEditorSyntaxHighlighting,
];
diff --git a/packages/editor-ui/src/components/ExpressionEditModal.vue b/packages/editor-ui/src/components/ExpressionEditModal.vue
index 07dcee3c3a..15fb08f88c 100644
--- a/packages/editor-ui/src/components/ExpressionEditModal.vue
+++ b/packages/editor-ui/src/components/ExpressionEditModal.vue
@@ -245,7 +245,6 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
margin-bottom: 0;
:global(.el-dialog__body) {
- background-color: var(--color-expression-editor-modal-background);
height: 100%;
padding: var(--spacing-s);
}
diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue
index bae21c58fe..07e1346545 100644
--- a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue
+++ b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue
@@ -7,20 +7,14 @@ import { computed, onMounted, ref, watch } from 'vue';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
import { n8nAutocompletion, n8nLang } from '@/plugins/codemirror/n8nLang';
import { forceParse } from '@/utils/forceParse';
-import { completionStatus } from '@codemirror/autocomplete';
import { inputTheme } from './theme';
import { useExpressionEditor } from '@/composables/useExpressionEditor';
-import {
- autocompleteKeyMap,
- enterKeyMap,
- historyKeyMap,
- tabKeyMap,
-} from '@/plugins/codemirror/keymap';
import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';
import type { Segment } from '@/types/expressions';
import { removeExpressionPrefix } from '@/utils/expressions';
import { mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
+import { editorKeymap } from '@/plugins/codemirror/keymap';
type Props = {
modelValue: string;
@@ -41,24 +35,7 @@ const emit = defineEmits<{
const root = ref();
const extensions = computed(() => [
inputTheme(props.isReadOnly),
- Prec.highest(
- keymap.of([
- ...tabKeyMap(),
- ...historyKeyMap,
- ...enterKeyMap,
- ...autocompleteKeyMap,
- {
- any: (view, event) => {
- if (event.key === 'Escape' && completionStatus(view.state) === null) {
- event.stopPropagation();
- emit('close');
- }
-
- return false;
- },
- },
- ]),
- ),
+ Prec.highest(keymap.of(editorKeymap)),
n8nLang(),
n8nAutocompletion(),
mappingDropCursor(),
@@ -66,7 +43,7 @@ const extensions = computed(() => [
history(),
expressionInputHandler(),
EditorView.lineWrapping,
- EditorView.domEventHandlers({ scroll: forceParse }),
+ EditorView.domEventHandlers({ scroll: (_, view) => forceParse(view) }),
infoBoxTooltips(),
]);
const editorValue = ref(removeExpressionPrefix(props.modelValue));
diff --git a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue
index 02fd40737e..91b9b0c35a 100644
--- a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue
+++ b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue
@@ -22,19 +22,14 @@ import htmlParser from 'prettier/plugins/html';
import cssParser from 'prettier/plugins/postcss';
import { computed, onBeforeUnmount, onMounted, ref, toRaw, toValue, watch } from 'vue';
-import { htmlEditorEventBus } from '@/event-bus';
import { useExpressionEditor } from '@/composables/useExpressionEditor';
+import { htmlEditorEventBus } from '@/event-bus';
import { n8nCompletionSources } from '@/plugins/codemirror/completions/addCompletions';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
-import {
- autocompleteKeyMap,
- enterKeyMap,
- historyKeyMap,
- tabKeyMap,
-} from '@/plugins/codemirror/keymap';
+import { editorKeymap } from '@/plugins/codemirror/keymap';
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
import { autoCloseTags, htmlLanguage } from 'codemirror-lang-html-n8n';
-import { codeNodeEditorTheme } from '../CodeNodeEditor/theme';
+import { codeEditorTheme } from '../CodeNodeEditor/theme';
import type { Range, Section } from './types';
import { nonTakenRanges } from './utils';
import { dropInExpressionEditor, mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
@@ -67,16 +62,13 @@ const extensions = computed(() => [
),
autoCloseTags,
expressionInputHandler(),
- Prec.highest(
- keymap.of([...tabKeyMap(), ...enterKeyMap, ...historyKeyMap, ...autocompleteKeyMap]),
- ),
+ Prec.highest(keymap.of(editorKeymap)),
indentOnInput(),
- codeNodeEditorTheme({
+ codeEditorTheme({
isReadOnly: props.isReadOnly,
maxHeight: props.fullscreen ? '100%' : '40vh',
minHeight: '20vh',
rows: props.rows,
- highlightColors: 'html',
}),
lineNumbers(),
highlightActiveLineGutter(),
diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/ExpressionOutput.vue b/packages/editor-ui/src/components/InlineExpressionEditor/ExpressionOutput.vue
index 1424b7d2e3..a0b88dff75 100644
--- a/packages/editor-ui/src/components/InlineExpressionEditor/ExpressionOutput.vue
+++ b/packages/editor-ui/src/components/InlineExpressionEditor/ExpressionOutput.vue
@@ -97,7 +97,7 @@ onMounted(() => {
extensions: [
EditorState.readOnly.of(true),
EditorView.lineWrapping,
- EditorView.domEventHandlers({ scroll: forceParse }),
+ EditorView.domEventHandlers({ scroll: (_, view) => forceParse(view) }),
...props.extensions,
],
}),
diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue
index 23cb31c7ea..72c430d24e 100644
--- a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue
+++ b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue
@@ -7,12 +7,7 @@ import { computed, ref, watch } from 'vue';
import { useExpressionEditor } from '@/composables/useExpressionEditor';
import { mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
-import {
- autocompleteKeyMap,
- enterKeyMap,
- historyKeyMap,
- tabKeyMap,
-} from '@/plugins/codemirror/keymap';
+import { editorKeymap } from '@/plugins/codemirror/keymap';
import { n8nAutocompletion, n8nLang } from '@/plugins/codemirror/n8nLang';
import { infoBoxTooltips } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';
import type { Segment } from '@/types/expressions';
@@ -42,9 +37,7 @@ const emit = defineEmits<{
const root = ref();
const extensions = computed(() => [
- Prec.highest(
- keymap.of([...tabKeyMap(false), ...enterKeyMap, ...autocompleteKeyMap, ...historyKeyMap]),
- ),
+ Prec.highest(keymap.of(editorKeymap)),
n8nLang(),
n8nAutocompletion(),
inputTheme({ isReadOnly: props.isReadOnly, rows: props.rows }),
diff --git a/packages/editor-ui/src/components/JsEditor/JsEditor.vue b/packages/editor-ui/src/components/JsEditor/JsEditor.vue
index 906a8476db..f8ef90cc3a 100644
--- a/packages/editor-ui/src/components/JsEditor/JsEditor.vue
+++ b/packages/editor-ui/src/components/JsEditor/JsEditor.vue
@@ -1,5 +1,5 @@