@sanity/code-input
Version:
Sanity input component for code, powered by CodeMirror
511 lines (510 loc) • 18.2 kB
JavaScript
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