remove codemirror dep and restructure a bit

This commit is contained in:
Gregory Schier
2025-05-15 09:28:14 -07:00
parent 21e2a67c1e
commit d962d7f94b
25 changed files with 278 additions and 2337 deletions

View File

@@ -2,16 +2,15 @@ import { defaultKeymap, historyField, indentWithTab } from '@codemirror/commands
import { foldState, forceParsing } from '@codemirror/language';
import type { EditorStateConfig, Extension } from '@codemirror/state';
import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import { EditorView, keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import { emacs } from '@replit/codemirror-emacs';
import { vim } from '@replit/codemirror-vim';
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
import { settingsAtom } from '@yaakapp-internal/models';
import type { EditorLanguage, TemplateFunction } from '@yaakapp-internal/plugins';
import { parseTemplate } from '@yaakapp-internal/templates';
import classNames from 'classnames';
import { EditorView } from 'codemirror';
import { useAtomValue } from 'jotai';
import { md5 } from 'js-md5';
import type { MutableRefObject, ReactNode } from 'react';
@@ -48,6 +47,8 @@ import {
import type { GenericCompletionConfig } from './genericCompletion';
import { singleLineExtensions } from './singleLine';
const { vim } = await import('@replit/codemirror-vim');
// VSCode's Tab actions mess with the single-line editor tab actions, so remove it.
const vsCodeWithoutTab = vscodeKeymap.filter((k) => k.key !== 'Tab');
@@ -365,7 +366,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
useEffect(() => {
if (cm.current === null) return;
const { view, languageCompartment } = cm.current;
const ext = getLanguageExtension({
getLanguageExtension({
useTemplating,
language,
environmentVariables,
@@ -374,8 +375,9 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
onClickVariable,
onClickMissingVariable,
onClickPathParameter,
}).then((ext) => {
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
});
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
}, [
language,
autocomplete,
@@ -399,7 +401,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
try {
const languageCompartment = new Compartment();
const langExt = getLanguageExtension({
getLanguageExtension({
useTemplating,
language,
completionOptions,
@@ -408,57 +410,57 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
onClickVariable,
onClickMissingVariable,
onClickPathParameter,
}).then((langExt) => {
const extensions = [
languageCompartment.of(langExt),
placeholderCompartment.current.of(placeholderExt(placeholderElFromText(placeholder))),
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : emptyExtension),
tabIndentCompartment.current.of(
!disableTabIndent ? keymap.of([indentWithTab]) : emptyExtension,
),
keymapCompartment.current.of(
keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'],
),
...getExtensions({
container,
readOnly,
singleLine,
hideGutter,
stateKey,
onChange: handleChange,
onPaste: handlePaste,
onPasteOverwrite: handlePasteOverwrite,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
...(extraExtensions ?? []),
];
const cachedJsonState = getCachedEditorState(defaultValue ?? '', stateKey);
const doc = `${defaultValue ?? ''}`;
const config: EditorStateConfig = { extensions, doc };
const state = cachedJsonState
? EditorState.fromJSON(cachedJsonState, config, stateFields)
: EditorState.create(config);
const view = new EditorView({ state, parent: container });
// For large documents, the parser may parse the max number of lines and fail to add
// things like fold markers because of it.
// This forces it to parse more but keeps the timeout to the default of 100 ms.
forceParsing(view, 9e6, 100);
cm.current = { view, languageCompartment };
if (autoFocus) {
view.focus();
}
if (autoSelect) {
view.dispatch({ selection: { anchor: 0, head: view.state.doc.length } });
}
});
const extensions = [
languageCompartment.of(langExt),
placeholderCompartment.current.of(placeholderExt(placeholderElFromText(placeholder))),
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : emptyExtension),
tabIndentCompartment.current.of(
!disableTabIndent ? keymap.of([indentWithTab]) : emptyExtension,
),
keymapCompartment.current.of(
keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'],
),
...getExtensions({
container,
readOnly,
singleLine,
hideGutter,
stateKey,
onChange: handleChange,
onPaste: handlePaste,
onPasteOverwrite: handlePasteOverwrite,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
...(extraExtensions ?? []),
];
const cachedJsonState = getCachedEditorState(defaultValue ?? '', stateKey);
const doc = `${defaultValue ?? ''}`;
const config: EditorStateConfig = { extensions, doc };
const state = cachedJsonState
? EditorState.fromJSON(cachedJsonState, config, stateFields)
: EditorState.create(config);
const view = new EditorView({ state, parent: container });
// For large documents, the parser may parse the max number of lines and fail to add
// things like fold markers because of it.
// This forces it to parse more but keeps the timeout to the default of 100 ms.
forceParsing(view, 9e6, 100);
cm.current = { view, languageCompartment };
if (autoFocus) {
view.focus();
}
if (autoSelect) {
view.dispatch({ selection: { anchor: 0, head: view.state.doc.length } });
}
} catch (e) {
console.log('Failed to initialize Codemirror', e);
}

View File

@@ -5,10 +5,6 @@ import {
completionKeymap,
} from '@codemirror/autocomplete';
import { history, historyKeymap } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import type { LanguageSupport } from '@codemirror/language';
import {
codeFolding,
@@ -27,6 +23,7 @@ import {
crosshairCursor,
drawSelection,
dropCursor,
EditorView,
highlightActiveLineGutter,
highlightSpecialChars,
keymap,
@@ -36,15 +33,11 @@ import {
import { tags as t } from '@lezer/highlight';
import type { EnvironmentVariable } from '@yaakapp-internal/models';
import { graphql } from 'cm6-graphql';
import { EditorView } from 'codemirror';
import { pluralizeCount } from '../../../lib/pluralize';
import type { EditorProps } from './Editor';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import type { TwigCompletionOption } from './twig/completion';
import { twig } from './twig/extension';
import { pathParametersPlugin } from './twig/pathParameters';
import { url } from './url/extension';
export const syntaxHighlightStyle = HighlightStyle.define([
{
@@ -75,21 +68,25 @@ const syntaxTheme = EditorView.theme({}, { dark: true });
const closeBracketsExtensions: Extension = [closeBrackets(), keymap.of([...closeBracketsKeymap])];
const syntaxExtensions: Record<NonNullable<EditorProps['language']>, LanguageSupport | null> = {
const syntaxExtensions: Record<
NonNullable<EditorProps['language']>,
null | (() => Promise<LanguageSupport>)
> = {
graphql: null,
json: json(),
javascript: javascript(),
html: xml(), // HTML as XML because HTML is oddly slow
xml: xml(),
url: url(),
pairs: pairs(),
text: text(),
markdown: markdown(),
json: () => import('@codemirror/lang-json').then((m) => m.json()),
javascript: () => import('@codemirror/lang-javascript').then((m) => m.javascript()),
// HTML as XML because HTML is oddly slow
html: () => import('@codemirror/lang-xml').then((m) => m.xml()),
xml: () => import('@codemirror/lang-xml').then((m) => m.xml()),
url: () => import('./url/extension').then((m) => m.url()),
pairs: () => import('./pairs/extension').then((m) => m.pairs()),
text: () => import('./text/extension').then((m) => m.text()),
markdown: () => import('@codemirror/lang-markdown').then((m) => m.markdown()),
};
const closeBracketsFor: (keyof typeof syntaxExtensions)[] = ['json', 'javascript', 'graphql'];
export function getLanguageExtension({
export async function getLanguageExtension({
useTemplating,
language = 'text',
environmentVariables,
@@ -122,12 +119,14 @@ export function getLanguageExtension({
return [graphql(), extraExtensions];
}
const base = syntaxExtensions[language ?? 'text'] ?? text();
const base_ = syntaxExtensions[language ?? 'text'] ?? text();
const base = typeof base_ === 'function' ? await base_() : text();
if (!useTemplating) {
return [base, extraExtensions];
}
const { twig } = await import('./twig/extension');
return twig({
base,
environmentVariables,

View File

@@ -1,6 +1,5 @@
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
import { Decoration, hoverTooltip, MatchDecorator, ViewPlugin } from '@codemirror/view';
import { EditorView } from 'codemirror';
import { Decoration, EditorView, hoverTooltip, MatchDecorator, ViewPlugin } from '@codemirror/view';
const REGEX =
/(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+*~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+*.~#?&/={}[\]]*))/g;

View File

@@ -1,8 +1,7 @@
import { syntaxTree } from '@codemirror/language';
import type { Range } from '@codemirror/state';
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
import { EditorView } from 'codemirror';
import { Decoration, ViewPlugin, WidgetType, EditorView } from '@codemirror/view';
class PathPlaceholderWidget extends WidgetType {
readonly #clickListenerCallback: () => void;

View File

@@ -1,9 +1,8 @@
import { syntaxTree } from '@codemirror/language';
import type { Range } from '@codemirror/state';
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
import { Decoration, ViewPlugin, WidgetType, EditorView } from '@codemirror/view';
import type { SyntaxNodeRef } from '@lezer/common';
import { EditorView } from 'codemirror';
import type { TwigCompletionOption } from './completion';
class TemplateTagWidget extends WidgetType {

View File

@@ -1,6 +1,6 @@
import type { EditorView } from '@codemirror/view';
import type { Color } from '@yaakapp/api';
import classNames from 'classnames';
import type { EditorView } from 'codemirror';
import type { ReactNode } from 'react';
import {
forwardRef,

View File

@@ -1,5 +1,5 @@
import type { EditorView } from '@codemirror/view';
import classNames from 'classnames';
import type { EditorView } from 'codemirror';
import {
forwardRef,
Fragment,