UNPKG

@sanity/code-input

Version:
511 lines (510 loc) • 18.2 kB
import { jsx } from "react/jsx-runtime"; import { c } from "react/compiler-runtime"; import { lineNumbers, EditorView, Decoration } from "@codemirror/view"; import { useRootTheme, useTheme, rem } from "@sanity/ui"; import CodeMirror from "@uiw/react-codemirror"; import { forwardRef, useState, useEffect, useContext } from "react"; import { CodeInputConfigContext } from "./index.js"; import { StreamLanguage } from "@codemirror/language"; import { StateField, StateEffect } from "@codemirror/state"; import { rgba } from "@sanity/ui/theme"; import { tags } from "@lezer/highlight"; import { createTheme } from "@uiw/codemirror-themes"; const defaultCodeModes = [{ name: "groq", loader: () => import("@codemirror/lang-javascript").then(({ javascriptLanguage }) => javascriptLanguage) }, { name: "javascript", loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !1 })) }, { name: "jsx", loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !0 })) }, { name: "typescript", loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !1, typescript: !0 })) }, { name: "tsx", loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !0, typescript: !0 })) }, { name: "php", loader: () => import("@codemirror/lang-php").then(({ php }) => php()) }, { name: "sql", loader: () => import("@codemirror/lang-sql").then(({ sql }) => sql()) }, { name: "mysql", loader: () => import("@codemirror/lang-sql").then(({ sql, MySQL }) => sql({ dialect: MySQL })) }, { name: "json", loader: () => import("@codemirror/lang-json").then(({ json }) => json()) }, { name: "markdown", loader: () => import("@codemirror/lang-markdown").then(({ markdown }) => markdown()) }, { name: "java", loader: () => import("@codemirror/lang-java").then(({ java }) => java()) }, { name: "html", loader: () => import("@codemirror/lang-html").then(({ html }) => html()) }, { name: "csharp", loader: () => import("@codemirror/legacy-modes/mode/clike").then(({ csharp }) => StreamLanguage.define(csharp)) }, { name: "sh", loader: () => import("@codemirror/legacy-modes/mode/shell").then(({ shell }) => StreamLanguage.define(shell)) }, { name: "css", loader: () => import("@codemirror/legacy-modes/mode/css").then(({ css }) => StreamLanguage.define(css)) }, { name: "scss", loader: () => import("@codemirror/legacy-modes/mode/css").then(({ css }) => StreamLanguage.define(css)) }, { name: "sass", loader: () => import("@codemirror/legacy-modes/mode/sass").then(({ sass }) => StreamLanguage.define(sass)) }, { name: "ruby", loader: () => import("@codemirror/legacy-modes/mode/ruby").then(({ ruby }) => StreamLanguage.define(ruby)) }, { name: "python", loader: () => import("@codemirror/legacy-modes/mode/python").then(({ python }) => StreamLanguage.define(python)) }, { name: "xml", loader: () => import("@codemirror/legacy-modes/mode/xml").then(({ xml }) => StreamLanguage.define(xml)) }, { name: "yaml", loader: () => import("@codemirror/legacy-modes/mode/yaml").then(({ yaml }) => StreamLanguage.define(yaml)) }, { name: "golang", loader: () => import("@codemirror/legacy-modes/mode/go").then(({ go }) => StreamLanguage.define(go)) }, { name: "text", loader: () => { } }, { name: "batch", loader: () => { } }]; function getBackwardsCompatibleTone(themeCtx) { return themeCtx.tone !== "neutral" && themeCtx.tone !== "suggest" ? themeCtx.tone : themeCtx.tone === "neutral" ? "default" : "primary"; } const highlightLineClass = "cm-highlight-line", addLineHighlight = StateEffect.define(), removeLineHighlight = StateEffect.define(), lineHighlightField = StateField.define({ create() { return Decoration.none; }, update(lines, tr) { lines = lines.map(tr.changes); for (const e of tr.effects) e.is(addLineHighlight) && (lines = lines.update({ add: [lineHighlightMark.range(e.value)] })), e.is(removeLineHighlight) && (lines = lines.update({ filter: (from) => from !== e.value })); return lines; }, toJSON(value, state) { const highlightLines = [], iter = value.iter(); for (; iter.value; ) { const lineNumber = state.doc.lineAt(iter.from).number; highlightLines.includes(lineNumber) || highlightLines.push(lineNumber), iter.next(); } return highlightLines; }, fromJSON(value, state) { const lines = state.doc.lines, highlights = value.filter((line) => line <= lines).map((line) => lineHighlightMark.range(state.doc.line(line).from)); highlights.sort((a, b) => a.from - b.from); try { return Decoration.none.update({ add: highlights }); } catch (e) { return console.error(e), Decoration.none; } }, provide: (f) => EditorView.decorations.from(f) }), lineHighlightMark = Decoration.line({ class: highlightLineClass }), highlightState = { highlight: lineHighlightField }; function createCodeMirrorTheme(options) { const { themeCtx } = options, fallbackTone = getBackwardsCompatibleTone(themeCtx), dark = { color: themeCtx.theme.color.dark[fallbackTone] }, light = { color: themeCtx.theme.color.light[fallbackTone] }; return EditorView.baseTheme({ ".cm-lineNumbers": { cursor: "default" }, ".cm-line.cm-line": { position: "relative" }, // need set background with pseudoelement so it does not render over selection color [`.${highlightLineClass}::before`]: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0, zIndex: -3, content: "''", boxSizing: "border-box" }, [`&dark .${highlightLineClass}::before`]: { background: rgba(dark.color.muted.caution.pressed.bg, 0.5) }, [`&light .${highlightLineClass}::before`]: { background: rgba(light.color.muted.caution.pressed.bg, 0.75) } }); } const highlightLine = (config) => { const highlightTheme = createCodeMirrorTheme({ themeCtx: config.theme }); return [lineHighlightField, config.readOnly ? [] : lineNumbers({ domEventHandlers: { mousedown: (editorView, lineInfo) => { const line = editorView.state.doc.lineAt(lineInfo.from); let isHighlighted = !1; return editorView.state.field(lineHighlightField).between(line.from, line.to, (_from, _to, value) => { if (value) return isHighlighted = !0, !1; }), isHighlighted ? editorView.dispatch({ effects: removeLineHighlight.of(line.from) }) : editorView.dispatch({ effects: addLineHighlight.of(line.from) }), config?.onHighlightChange && config.onHighlightChange(editorView.state.toJSON(highlightState).highlight), !0; } } }), highlightTheme]; }; function setHighlightedLines(view, highlightLines) { const doc = view.state.doc, lines = doc.lines, allLineNumbers = Array.from({ length: lines }, (_x, i) => i + 1); view.dispatch({ effects: allLineNumbers.map((lineNumber) => { const line = doc.line(lineNumber); return highlightLines?.includes(lineNumber) ? addLineHighlight.of(line.from) : removeLineHighlight.of(line.from); }) }); } function useThemeExtension() { const $ = c(6), themeCtx = useRootTheme(); let t0; $[0] !== themeCtx ? (t0 = getBackwardsCompatibleTone(themeCtx), $[0] = themeCtx, $[1] = t0) : t0 = $[1]; const fallbackTone = t0, t1 = themeCtx.theme.color.dark[fallbackTone]; let t2; if ($[2] !== fallbackTone || $[3] !== t1 || $[4] !== themeCtx.theme.color.light) { const dark = { color: t1 }, light = { color: themeCtx.theme.color.light[fallbackTone] }; t2 = EditorView.baseTheme({ "&.cm-editor": { height: "100%" }, "&.cm-editor.cm-focused": { outline: "none" }, "&.cm-editor.cm-focused .cm-matchingBracket": { backgroundColor: "transparent" }, "&.cm-editor.cm-focused .cm-nonmatchingBracket": { backgroundColor: "transparent" }, "&dark.cm-editor.cm-focused .cm-matchingBracket": { outline: `1px solid ${dark.color.base.border}` }, "&dark.cm-editor.cm-focused .cm-nonmatchingBracket": { outline: `1px solid ${dark.color.base.border}` }, "&light.cm-editor.cm-focused .cm-matchingBracket": { outline: `1px solid ${light.color.base.border}` }, "&light.cm-editor.cm-focused .cm-nonmatchingBracket": { outline: `1px solid ${light.color.base.border}` }, "& .cm-lineNumbers .cm-gutterElement": { minWidth: "32px !important", padding: "0 8px !important" }, "& .cm-gutter.cm-foldGutter": { width: "0px !important" }, "&dark .cm-gutters": { color: `${rgba(dark.color.card.enabled.code.fg, 0.5)} !important`, borderRight: `1px solid ${rgba(dark.color.base.border, 0.5)}` }, "&light .cm-gutters": { color: `${rgba(light.color.card.enabled.code.fg, 0.5)} !important`, borderRight: `1px solid ${rgba(light.color.base.border, 0.5)}` } }), $[2] = fallbackTone, $[3] = t1, $[4] = themeCtx.theme.color.light, $[5] = t2; } else t2 = $[5]; return t2; } function useCodeMirrorTheme() { const $ = c(19), theme = useTheme(), { code: codeFont } = theme.sanity.fonts, { base, card, dark, syntax } = theme.sanity.color, t0 = dark ? "dark" : "light"; let t1; return $[0] !== base.focusRing || $[1] !== card.disabled.bg || $[2] !== card.disabled.code.fg || $[3] !== card.enabled.bg || $[4] !== card.enabled.code.fg || $[5] !== card.enabled.fg || $[6] !== codeFont.family || $[7] !== syntax.attrName || $[8] !== syntax.boolean || $[9] !== syntax.className || $[10] !== syntax.comment || $[11] !== syntax.function || $[12] !== syntax.keyword || $[13] !== syntax.number || $[14] !== syntax.operator || $[15] !== syntax.property || $[16] !== syntax.string || $[17] !== t0 ? (t1 = createTheme({ theme: t0, settings: { background: card.enabled.bg, foreground: card.enabled.code.fg, lineHighlight: card.enabled.bg, fontFamily: codeFont.family, caret: base.focusRing, selection: rgba(base.focusRing, 0.2), selectionMatch: rgba(base.focusRing, 0.4), gutterBackground: card.disabled.bg, gutterForeground: card.disabled.code.fg, gutterActiveForeground: card.enabled.fg }, styles: [{ tag: [tags.heading, tags.heading2, tags.heading3, tags.heading4, tags.heading5, tags.heading6], color: card.enabled.fg }, { tag: tags.angleBracket, color: card.enabled.code.fg }, { tag: tags.atom, color: syntax.keyword }, { tag: tags.attributeName, color: syntax.attrName }, { tag: tags.bool, color: syntax.boolean }, { tag: tags.bracket, color: card.enabled.code.fg }, { tag: tags.className, color: syntax.className }, { tag: tags.comment, color: syntax.comment }, { tag: tags.definition(tags.typeName), color: syntax.function }, { tag: [tags.definition(tags.variableName), tags.function(tags.variableName), tags.className, tags.attributeName], color: syntax.function }, { tag: [tags.function(tags.propertyName), tags.propertyName], color: syntax.function }, { tag: tags.keyword, color: syntax.keyword }, { tag: tags.null, color: syntax.number }, { tag: tags.number, color: syntax.number }, { tag: tags.meta, color: card.enabled.code.fg }, { tag: tags.operator, color: syntax.operator }, { tag: tags.propertyName, color: syntax.property }, { tag: [tags.string, tags.special(tags.brace)], color: syntax.string }, { tag: tags.tagName, color: syntax.className }, { tag: tags.typeName, color: syntax.keyword }] }), $[0] = base.focusRing, $[1] = card.disabled.bg, $[2] = card.disabled.code.fg, $[3] = card.enabled.bg, $[4] = card.enabled.code.fg, $[5] = card.enabled.fg, $[6] = codeFont.family, $[7] = syntax.attrName, $[8] = syntax.boolean, $[9] = syntax.className, $[10] = syntax.comment, $[11] = syntax.function, $[12] = syntax.keyword, $[13] = syntax.number, $[14] = syntax.operator, $[15] = syntax.property, $[16] = syntax.string, $[17] = t0, $[18] = t1) : t1 = $[18], t1; } function useFontSizeExtension(props) { const $ = c(3), { fontSize: fontSizeProp } = props, theme = useTheme(), { code: codeFont } = theme.sanity.fonts, { fontSize, lineHeight } = codeFont.sizes[fontSizeProp] || codeFont.sizes[2]; let t0; return $[0] !== fontSize || $[1] !== lineHeight ? (t0 = EditorView.baseTheme({ "&": { fontSize: rem(fontSize) }, "& .cm-scroller": { lineHeight: `${lineHeight / fontSize} !important` } }), $[0] = fontSize, $[1] = lineHeight, $[2] = t0) : t0 = $[2], t0; } const CodeMirrorProxy = forwardRef(function(props, ref) { const $ = c(41); let basicSetupProp, codeMirrorProps, highlightLines, languageMode, onHighlightChange, readOnly, value; $[0] !== props ? ({ basicSetup: basicSetupProp, highlightLines, languageMode, onHighlightChange, readOnly, value, ...codeMirrorProps } = props, $[0] = props, $[1] = basicSetupProp, $[2] = codeMirrorProps, $[3] = highlightLines, $[4] = languageMode, $[5] = onHighlightChange, $[6] = readOnly, $[7] = value) : (basicSetupProp = $[1], codeMirrorProps = $[2], highlightLines = $[3], languageMode = $[4], onHighlightChange = $[5], readOnly = $[6], value = $[7]); const themeCtx = useRootTheme(), codeMirrorTheme = useCodeMirrorTheme(), [editorView, setEditorView] = useState(void 0), themeExtension = useThemeExtension(); let t0; $[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = { fontSize: 1 }, $[8] = t0) : t0 = $[8]; const fontSizeExtension = useFontSizeExtension(t0), languageExtension = useLanguageExtension(languageMode); let t1; $[9] !== onHighlightChange || $[10] !== readOnly || $[11] !== themeCtx ? (t1 = highlightLine({ onHighlightChange, readOnly, theme: themeCtx }), $[9] = onHighlightChange, $[10] = readOnly, $[11] = themeCtx, $[12] = t1) : t1 = $[12]; const highlightLineExtension = t1; let t2; bb0: { let t32; $[13] !== fontSizeExtension || $[14] !== highlightLineExtension || $[15] !== themeExtension ? (t32 = [themeExtension, fontSizeExtension, highlightLineExtension, EditorView.lineWrapping], $[13] = fontSizeExtension, $[14] = highlightLineExtension, $[15] = themeExtension, $[16] = t32) : t32 = $[16]; const baseExtensions = t32; if (languageExtension) { let t42; $[17] !== baseExtensions || $[18] !== languageExtension ? (t42 = [...baseExtensions, languageExtension], $[17] = baseExtensions, $[18] = languageExtension, $[19] = t42) : t42 = $[19], t2 = t42; break bb0; } t2 = baseExtensions; } const extensions = t2; let t3; $[20] !== editorView || $[21] !== highlightLines ? (t3 = () => { editorView && setHighlightedLines(editorView, highlightLines ?? []); }, $[20] = editorView, $[21] = highlightLines, $[22] = t3) : t3 = $[22]; let t4; $[23] !== editorView || $[24] !== highlightLines || $[25] !== value ? (t4 = [editorView, highlightLines, value], $[23] = editorView, $[24] = highlightLines, $[25] = value, $[26] = t4) : t4 = $[26], useEffect(t3, t4); let t5; $[27] !== highlightLines || $[28] !== value ? (t5 = () => ({ json: { doc: value ?? "", selection: { main: 0, ranges: [{ anchor: 0, head: 0 }] }, highlight: highlightLines ?? [] }, fields: highlightState }), $[27] = highlightLines, $[28] = value, $[29] = t5) : t5 = $[29]; const [initialState] = useState(t5); let t6; $[30] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t6 = (view) => { setEditorView(view); }, $[30] = t6) : t6 = $[30]; const handleCreateEditor = t6; let t7; $[31] !== basicSetupProp ? (t7 = basicSetupProp ?? { highlightActiveLine: !1 }, $[31] = basicSetupProp, $[32] = t7) : t7 = $[32]; const basicSetup = t7; let t8; return $[33] !== basicSetup || $[34] !== codeMirrorProps || $[35] !== codeMirrorTheme || $[36] !== extensions || $[37] !== initialState || $[38] !== ref || $[39] !== value ? (t8 = /* @__PURE__ */ jsx(CodeMirror, { ...codeMirrorProps, value, ref, extensions, theme: codeMirrorTheme, onCreateEditor: handleCreateEditor, initialState, basicSetup }), $[33] = basicSetup, $[34] = codeMirrorProps, $[35] = codeMirrorTheme, $[36] = extensions, $[37] = initialState, $[38] = ref, $[39] = value, $[40] = t8) : t8 = $[40], t8; }); function useLanguageExtension(mode) { const $ = c(6), codeConfig = useContext(CodeInputConfigContext), [languageExtension, setLanguageExtension] = useState(); let t0; $[0] !== codeConfig?.codeModes || $[1] !== mode ? (t0 = () => { const codeMode = [...codeConfig?.codeModes ?? [], ...defaultCodeModes].find((m) => m.name === mode); codeMode?.loader || console.warn(`Found no codeMode for language mode ${mode}, syntax highlighting will be disabled.`); let active = !0; return Promise.resolve(codeMode?.loader()).then((extension) => { active && setLanguageExtension(extension); }).catch((e) => { console.error(`Failed to load language mode ${mode}`, e), active && setLanguageExtension(void 0); }), () => { active = !1; }; }, $[0] = codeConfig?.codeModes, $[1] = mode, $[2] = t0) : t0 = $[2]; let t1; return $[3] !== codeConfig || $[4] !== mode ? (t1 = [mode, codeConfig], $[3] = codeConfig, $[4] = mode, $[5] = t1) : t1 = $[5], useEffect(t0, t1), languageExtension; } export { CodeMirrorProxy as default }; //# sourceMappingURL=CodeMirrorProxy.js.map