Files
portainer/app/react/components/CodeEditor/useCodeEditorExtensions.ts
T

94 lines
3.1 KiB
TypeScript

import { useMemo } from 'react';
import {
StreamLanguage,
LanguageSupport,
indentService,
} from '@codemirror/language';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { keymap, Extension } from '@uiw/react-codemirror';
import { highlightSpecialChars, lineNumbers } from '@codemirror/view';
import type { JSONSchema7 } from 'json-schema';
import { lintKeymap, lintGutter } from '@codemirror/lint';
import { defaultKeymap } from '@codemirror/commands';
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
import { yamlCompletion, yamlSchema } from 'yaml-schema';
import { compact } from 'lodash';
export type CodeEditorType = 'yaml' | 'shell' | 'dockerfile';
// Extends the default special-char set with invisible Unicode characters that
// are hard to spot (non-breaking spaces, zero-width joiners, word joiner, etc.)
export const extendedHighlightSpecialChars = highlightSpecialChars({
// eslint-disable-next-line no-misleading-character-class
addSpecialChars: /[\u00a0\u200c\u200d\u202f\u2060]/,
});
// Custom indentation service for YAML
const yamlIndentExtension = indentService.of((context, pos) => {
const prevLine = context.lineAt(pos, -1);
const prevIndent = /^\s*/.exec(prevLine.text)?.[0].length || 0;
if (/:\s*$/.test(prevLine.text)) {
return prevIndent + 2;
}
return prevIndent;
});
const dockerFileLanguage = new LanguageSupport(
StreamLanguage.define(dockerFile)
);
const shellLanguage = new LanguageSupport(StreamLanguage.define(shell));
function yamlLanguage(schema?: JSONSchema7) {
const [yaml, linter, , , stateExtensions] = yamlSchema(schema);
return compact([
yaml,
linter,
stateExtensions,
yamlIndentExtension,
// explicitly setting lineNumbers() as an extension ensures that the gutter order is the same between the diff viewer and the code editor
lineNumbers(),
!!schema && lintGutter(),
keymap.of([...defaultKeymap, ...completionKeymap, ...lintKeymap]),
// only show completions when a schema is provided
!!schema &&
autocompletion({
icons: false,
activateOnTypingDelay: 300,
selectOnOpen: true,
activateOnTyping: true,
override: [
(ctx) => {
const getCompletions = yamlCompletion();
const completions = getCompletions(ctx);
if (Array.isArray(completions)) {
return null;
}
completions.validFor = /^\w*$/;
return completions;
},
],
}),
]);
}
export function useCodeEditorExtensions(
type?: CodeEditorType,
schema?: JSONSchema7
): Extension[] {
return useMemo(() => {
const baseExtensions = [extendedHighlightSpecialChars];
switch (type) {
case 'dockerfile':
return [...baseExtensions, dockerFileLanguage];
case 'shell':
return [...baseExtensions, shellLanguage];
case 'yaml':
return [...baseExtensions, ...yamlLanguage(schema)];
default:
return baseExtensions;
}
}, [type, schema]);
}