This commit is contained in:
Elias Meire 2024-10-30 09:21:38 +01:00
parent 42966628d3
commit 406aeb005a
No known key found for this signature in database
8 changed files with 1516 additions and 41 deletions

View file

@ -4,11 +4,10 @@ import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
import { format } from 'prettier'; import { format } from 'prettier';
import jsParser from 'prettier/plugins/babel'; import jsParser from 'prettier/plugins/babel';
import * as estree from 'prettier/plugins/estree'; import * as estree from 'prettier/plugins/estree';
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue';
import { CODE_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { codeNodeEditorEventBus } from '@/event-bus'; import { codeNodeEditorEventBus } from '@/event-bus';
import { usePostHog } from '@/stores/posthog.store';
import { useRootStore } from '@/stores/root.store'; import { useRootStore } from '@/stores/root.store';
import { useCodeEditor } from '@/composables/useCodeEditor'; import { useCodeEditor } from '@/composables/useCodeEditor';
@ -18,6 +17,8 @@ import { useTelemetry } from '@/composables/useTelemetry';
import AskAI from './AskAI/AskAI.vue'; import AskAI from './AskAI/AskAI.vue';
import { CODE_PLACEHOLDERS } from './constants'; import { CODE_PLACEHOLDERS } from './constants';
import { useLinter } from './linter'; import { useLinter } from './linter';
import { useSettingsStore } from '@/stores/settings.store';
import { dropInCodeEditor } from '@/plugins/codemirror/dragAndDrop';
type Props = { type Props = {
mode: CodeExecutionMode; mode: CodeExecutionMode;
@ -59,8 +60,11 @@ const linter = useLinter(
); );
const extensions = computed(() => [linter.value]); const extensions = computed(() => [linter.value]);
const placeholder = computed(() => CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? ''); const placeholder = computed(() => CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? '');
const dragAndDropEnabled = computed(() => {
return !props.isReadOnly && props.mode === 'runOnceForEachItem';
});
const { highlightLine, readEditorValue } = useCodeEditor({ const { highlightLine, readEditorValue, editor } = useCodeEditor({
editorRef: codeNodeEditorRef, editorRef: codeNodeEditorRef,
language: () => props.language, language: () => props.language,
editorValue: () => props.modelValue, editorValue: () => props.modelValue,

View file

@ -19,6 +19,7 @@ import { editorKeymap } from '@/plugins/codemirror/keymap';
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang'; import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { codeEditorTheme } from '../CodeNodeEditor/theme'; import { codeEditorTheme } from '../CodeNodeEditor/theme';
import { mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
type Props = { type Props = {
modelValue: string; modelValue: string;

View file

@ -2,7 +2,7 @@ import { codeEditorTheme } from '@/components/CodeNodeEditor/theme';
import { editorKeymap } from '@/plugins/codemirror/keymap'; import { editorKeymap } from '@/plugins/codemirror/keymap';
import { typescript } from '@/plugins/codemirror/lsp/typescript'; import { typescript } from '@/plugins/codemirror/lsp/typescript';
import { closeCursorInfoBox } from '@/plugins/codemirror/tooltips/InfoBoxTooltip'; import { closeCursorInfoBox } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';
import { closeCompletion, completionStatus } from '@codemirror/autocomplete'; import { closeBrackets, closeCompletion, completionStatus } from '@codemirror/autocomplete';
import { history } from '@codemirror/commands'; import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json'; import { json } from '@codemirror/lang-json';
@ -40,6 +40,7 @@ import {
type MaybeRefOrGetter, type MaybeRefOrGetter,
type Ref, type Ref,
} from 'vue'; } from 'vue';
import { mappingDropCursor } from '../plugins/codemirror/dragAndDrop';
export type CodeEditorLanguage = 'json' | 'html' | 'javaScript' | 'python'; export type CodeEditorLanguage = 'json' | 'html' | 'javaScript' | 'python';
@ -232,7 +233,9 @@ export const useCodeEditor = ({
dropCursor(), dropCursor(),
indentOnInput(), indentOnInput(),
bracketMatching(), bracketMatching(),
closeBrackets(),
highlightActiveLineGutter(), highlightActiveLineGutter(),
mappingDropCursor(),
indentationMarkers({ indentationMarkers({
highlightActiveBlock: true, highlightActiveBlock: true,
markerType: 'fullScope', markerType: 'fullScope',

View file

@ -1,4 +1,4 @@
import { autocompletion, completeFromList, type CompletionSource } from '@codemirror/autocomplete'; import { autocompletion, type CompletionSource } from '@codemirror/autocomplete';
import { javascriptLanguage } from '@codemirror/lang-javascript'; import { javascriptLanguage } from '@codemirror/lang-javascript';
import { linter, type LintSource } from '@codemirror/lint'; import { linter, type LintSource } from '@codemirror/lint';
import { combineConfig, Facet, type Extension } from '@codemirror/state'; import { combineConfig, Facet, type Extension } from '@codemirror/state';
@ -17,13 +17,23 @@ export const tsFacet = Facet.define<
const tsCompletions: CompletionSource = async (context) => { const tsCompletions: CompletionSource = async (context) => {
const { worker } = context.state.facet(tsFacet); const { worker } = context.state.facet(tsFacet);
console.log('complete', context); const { pos, explicit } = context;
let word = context.matchBefore(/\w*/);
if (!word?.text) {
word = context.matchBefore(/\./);
}
if (!word?.text && !explicit) return null;
const result = await worker.getCompletionsAtPos(context.pos); const result = await worker.getCompletionsAtPos(context.pos);
if (!result) return result; if (!result) return result;
return await completeFromList(result.options)(context); return {
from: word ? (word.text === '.' ? word.to : word.from) : pos,
options: result.options,
};
}; };
const tsLint: LintSource = async (view) => { const tsLint: LintSource = async (view) => {

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,9 @@ import {
cmPosToTs, cmPosToTs,
} from './utils'; } from './utils';
import type { Completion } from '@codemirror/autocomplete'; import type { Completion } from '@codemirror/autocomplete';
import types from './types.d.ts?raw';
const TS_COMPLETE_BLOCKLIST: ts.ScriptElementKind[] = [ts.ScriptElementKind.warning];
const worker = (): LanguageServiceWorker => { const worker = (): LanguageServiceWorker => {
let env: tsvfs.VirtualTypeScriptEnvironment; let env: tsvfs.VirtualTypeScriptEnvironment;
@ -21,12 +24,11 @@ const worker = (): LanguageServiceWorker => {
allowJs: true, allowJs: true,
checkJs: true, checkJs: true,
target: ts.ScriptTarget.ESNext, target: ts.ScriptTarget.ESNext,
lib: ['ESNext'], noLib: true,
module: ts.ModuleKind.ESNext, module: ts.ModuleKind.ESNext,
strict: true, strict: true,
typeRoots: [],
types: [],
importHelpers: false, importHelpers: false,
skipDefaultLibCheck: true,
noEmit: true, noEmit: true,
}; };
@ -40,15 +42,8 @@ const worker = (): LanguageServiceWorker => {
await indexedDbCache('typescript-cache', 'fs-map'), await indexedDbCache('typescript-cache', 'fs-map'),
); );
fsMap.set('types.d.ts', types);
fsMap.set(FILE_NAME, wrapInFunction(content)); fsMap.set(FILE_NAME, wrapInFunction(content));
fsMap.set(
'types.d.ts',
`export {};
declare global {
const $input: { json: Record<string,any>, all: () => [] }
}`,
);
const system = tsvfs.createSystem(fsMap); const system = tsvfs.createSystem(fsMap);
env = tsvfs.createVirtualTypeScriptEnvironment( env = tsvfs.createVirtualTypeScriptEnvironment(
@ -76,13 +71,19 @@ declare global {
if (!completionInfo) return null; if (!completionInfo) return null;
const options = completionInfo.entries.map((entry): Completion => { const options = completionInfo.entries
const boost = -Number(entry.sortText) || 0; .filter(
return { (entry) =>
label: entry.name, !TS_COMPLETE_BLOCKLIST.includes(entry.kind) &&
boost, (entry.sortText < '15' || completionInfo.optionalReplacementSpan?.length),
}; )
}); .map((entry): Completion => {
const boost = -Number(entry.sortText) || 0;
return {
label: entry.name,
boost,
};
});
return { return {
from: pos, from: pos,

View file

@ -1,7 +1,7 @@
import type { Diagnostic } from '@codemirror/lint'; import type { Diagnostic } from '@codemirror/lint';
import ts from 'typescript'; import ts from 'typescript';
export const FILE_NAME = 'index.ts'; export const FILE_NAME = 'index.js';
const FN_PREFIX = '(() => {\n'; const FN_PREFIX = '(() => {\n';
export function wrapInFunction(script: string): string { export function wrapInFunction(script: string): string {
@ -9,7 +9,7 @@ export function wrapInFunction(script: string): string {
} }
export function cmPosToTs(pos: number) { export function cmPosToTs(pos: number) {
return pos + FN_PREFIX.length - 1; return pos + FN_PREFIX.length;
} }
export function tsPosToCm(pos: number) { export function tsPosToCm(pos: number) {

View file

@ -26,5 +26,6 @@
// TODO: remove all options below this line // TODO: remove all options below this line
"useUnknownInCatchVariables": false "useUnknownInCatchVariables": false
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
"exclude": ["src/plugins/codemirror/lsp/worker/**/*.d.ts"]
} }