style(代码配色): True / False / None 和 NULL 改成关键字蓝;SQL 的 COUNT、AVG、ROUND 这些函数是函数色;编辑器的选区、行号、当前行、括号配对也换成 VS Code 的样子
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
True / None 原来和数字一个绿色,VS Code 里是 constant.language,和关键字同色。 SQL 的解析器把 COUNT 当关键字、把 avg 当普通名字,现在后面跟着括号的常用函数一律是函数色。 编辑器的框原来沿用 smoothy / One Dark(亮色选区是淡黄),现在照 VS Code 的默认值:选区淡蓝 / 深蓝、行号青色、当前行是一圈边框。当前行只在有焦点时画,免得自学一课十几个代码块每块第一行都框着。 暗色的底色仍用网站的。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60e96f311e
commit
a085351a2f
6 files changed
+109
-36
No files matched your search
@@ -12,12 +12,14 @@
|
||||
* 不标的话 `“你好”` 会被认成变量名,看着和正常代码一样。
|
||||
* - Python 的内置类型(int、str、list…):照 VS Code 一律是类型色。lezer 只按位置分,
|
||||
* `int(input())` 里是函数、`map(int, …)` 里是变量,同一行两个颜色。
|
||||
* - SQL 的函数(COUNT、AVG、ROUND…,后面跟着括号的):照 VS Code 是函数色。lezer 的 SQL 解析器
|
||||
* 把 COUNT 当关键字、把 avg 当普通名字,同是聚合函数一个蓝一个没颜色。
|
||||
*/
|
||||
import type { syntaxTree } from "@codemirror/language"
|
||||
|
||||
type Tree = ReturnType<typeof syntaxTree>
|
||||
|
||||
export type CodeFamily = "c" | "python"
|
||||
export type CodeFamily = "c" | "python" | "sql"
|
||||
|
||||
export interface Mark {
|
||||
from: number
|
||||
@@ -25,7 +27,7 @@ export interface Mark {
|
||||
cls: string
|
||||
}
|
||||
|
||||
const FORMAT: Record<CodeFamily, RegExp> = {
|
||||
const FORMAT: Partial<Record<CodeFamily, RegExp>> = {
|
||||
c: /%(\d+\$)?[#0\- +']*(-?\d+|\*)?(\.(-?\d+|\*)?)?(hh|h|ll|l|j|t|z|L)?[diouxXeEfFgGaAcspn%]/g,
|
||||
python: /%(\([\w\s]*\))?[-+#0 ]*(\d+|\*)?(\.(\d+|\*)?)?[hlL]?[diouxXeEfFgGcrsa%]/g,
|
||||
}
|
||||
@@ -33,6 +35,7 @@ const FORMAT: Record<CodeFamily, RegExp> = {
|
||||
const FORMAT_CLASS: Record<CodeFamily, string> = {
|
||||
c: "tok-format",
|
||||
python: "tok-format-py",
|
||||
sql: "",
|
||||
}
|
||||
|
||||
// 全角的 ASCII(!(),:;?a1…)、全角空格、、。〈〉《》「」【】、中文引号
|
||||
@@ -61,21 +64,55 @@ const PY_BUILTIN_TYPES = new Set([
|
||||
"type",
|
||||
])
|
||||
|
||||
// VS Code 的 SQL 语法里 support.function 的那批(SQLite 认的)
|
||||
const SQL_FUNCTIONS = new Set([
|
||||
"abs",
|
||||
"avg",
|
||||
"coalesce",
|
||||
"count",
|
||||
"date",
|
||||
"datetime",
|
||||
"group_concat",
|
||||
"ifnull",
|
||||
"instr",
|
||||
"length",
|
||||
"lower",
|
||||
"ltrim",
|
||||
"max",
|
||||
"min",
|
||||
"nullif",
|
||||
"random",
|
||||
"replace",
|
||||
"round",
|
||||
"rtrim",
|
||||
"strftime",
|
||||
"substr",
|
||||
"sum",
|
||||
"time",
|
||||
"total",
|
||||
"trim",
|
||||
"upper",
|
||||
])
|
||||
|
||||
function matches(text: string, re: RegExp, cls: string): Mark[] {
|
||||
return [...text.matchAll(re)].map((m) => ({ from: m.index, to: m.index + m[0].length, cls }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个词(lezer 吐出的一段文字和它的类名)上要补的标记,位置相对 text 开头。
|
||||
* prev 是这段文字前面那个字符,用来排除 `obj.int` 这种属性
|
||||
* prev 是这段文字前面那个字符(排除 `obj.int` 这种属性),after 是后面紧跟的一小截(看是不是 `(`)
|
||||
*/
|
||||
export function extraMarks(
|
||||
text: string,
|
||||
classes: string,
|
||||
family: CodeFamily,
|
||||
prev: string,
|
||||
after: string,
|
||||
): Mark[] {
|
||||
if (classes.includes("tok-string")) return matches(text, FORMAT[family], FORMAT_CLASS[family])
|
||||
if (classes.includes("tok-string")) {
|
||||
const re = FORMAT[family]
|
||||
return re ? matches(text, re, FORMAT_CLASS[family]) : []
|
||||
}
|
||||
if (classes.includes("tok-comment")) return []
|
||||
if (
|
||||
family === "python" &&
|
||||
@@ -85,7 +122,18 @@ export function extraMarks(
|
||||
) {
|
||||
return [{ from: 0, to: text.length, cls: "tok-builtin-type" }]
|
||||
}
|
||||
return matches(text, FULL_WIDTH, "tok-fullwidth")
|
||||
const marks = matches(text, FULL_WIDTH, "tok-fullwidth")
|
||||
// 没类名的部分是一长截(`), round(avg(score), `),所以在里面逐个找词
|
||||
if (family === "sql" && (classes === "" || classes === "tok-keyword")) {
|
||||
for (const m of text.matchAll(/(?<!\w)[A-Za-z_]\w*/g)) {
|
||||
const end = m.index + m[0].length
|
||||
if (SQL_FUNCTIONS.has(m[0].toLowerCase()) && /^\s*\(/.test(text.slice(end) + after)) {
|
||||
marks.push({ from: m.index, to: end, cls: "tok-sql-function" })
|
||||
}
|
||||
}
|
||||
marks.sort((a, b) => a.from - b.from)
|
||||
}
|
||||
return marks
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,10 +168,11 @@ export function splitMarks(
|
||||
marksFor: string,
|
||||
family: CodeFamily,
|
||||
prev: string,
|
||||
after: string,
|
||||
put: (text: string, classes: string) => void,
|
||||
) {
|
||||
let at = 0
|
||||
for (const m of extraMarks(text, marksFor, family, prev)) {
|
||||
for (const m of extraMarks(text, marksFor, family, prev, after)) {
|
||||
if (m.from > at) put(text.slice(at, m.from), classes)
|
||||
put(text.slice(m.from, m.to), classes ? `${classes} ${m.cls}` : m.cls)
|
||||
at = m.to
|
||||
|
||||
@@ -6,7 +6,7 @@ import { type CodeFamily, extraMarks, fakeStrings } from "../codeMarks"
|
||||
import { codeHighlighter } from "../highlight"
|
||||
|
||||
/**
|
||||
* 编辑器里补的标记(%d、全角标点、Python 内置类型),规则见 shared/codeMarks.ts。
|
||||
* 编辑器里补的标记(%d、全角标点、Python 内置类型、SQL 函数),规则见 shared/codeMarks.ts。
|
||||
* 和静态代码一样按 codeHighlighter 分出来的词逐个过 extraMarks();只扫看得见的部分。
|
||||
*/
|
||||
export function codeMarks(family: CodeFamily): Extension {
|
||||
@@ -31,6 +31,7 @@ export function codeMarks(family: CodeFamily): Extension {
|
||||
classes,
|
||||
family,
|
||||
prev(start),
|
||||
view.state.doc.sliceString(end, end + 20),
|
||||
)) {
|
||||
ranges.push(deco(m.cls).range(start + m.from, start + m.to))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import { ojPythonLanguage } from "./pythonLanguage"
|
||||
* Java / Golang / JavaScript 没有单独的包,落到 cpp(),是既有行为,不是遗漏。
|
||||
*/
|
||||
export function languageExtension(language: LANGUAGE): Extension {
|
||||
if (language === "SQL") return sql({ dialect: SQLite, upperCaseKeywords: true })
|
||||
if (language === "SQL")
|
||||
return [sql({ dialect: SQLite, upperCaseKeywords: true }), codeMarks("sql")]
|
||||
if (language === "Python") {
|
||||
return [new LanguageSupport(ojPythonLanguage, python().support), codeMarks("python")]
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ html.dark {
|
||||
.tok-builtin-type * {
|
||||
color: var(--code-type);
|
||||
}
|
||||
.tok-sql-function,
|
||||
.tok-sql-function * {
|
||||
color: var(--code-function);
|
||||
}
|
||||
.tok-fullwidth,
|
||||
.tok-fullwidth * {
|
||||
color: var(--code-invalid);
|
||||
|
||||
@@ -20,12 +20,16 @@ export const codeHighlighter = tagHighlighter([
|
||||
// 分法照 VS Code:if / return / #include 这类管流程的词单独一色,`int` / `struct` 是普通关键字,
|
||||
// 自定义的类型(struct 名)才是类型色;运算符不上色,变量名上色
|
||||
{ tag: [t.controlKeyword, t.moduleKeyword, t.processingInstruction], class: "tok-control" },
|
||||
{ tag: [t.keyword, t.modifier, t.standard(t.typeName)], class: "tok-keyword" },
|
||||
// True / False / None / self / NULL 照 VS Code 是 constant.language,和关键字一个颜色
|
||||
{
|
||||
tag: [t.keyword, t.modifier, t.standard(t.typeName), t.bool, t.null, t.atom, t.self],
|
||||
class: "tok-keyword",
|
||||
},
|
||||
{ tag: t.escape, class: "tok-escape" },
|
||||
{ tag: [t.function(t.variableName), t.function(t.propertyName)], class: "tok-function" },
|
||||
{ tag: [t.variableName, t.propertyName], class: "tok-variable" },
|
||||
{ tag: t.string, class: "tok-string" },
|
||||
{ tag: [t.number, t.bool, t.null, t.atom, t.self], class: "tok-number" },
|
||||
{ tag: t.number, class: "tok-number" },
|
||||
{ tag: [t.typeName, t.className, t.namespace, t.standard(t.name)], class: "tok-type" },
|
||||
{ tag: t.comment, class: "tok-comment" },
|
||||
{ tag: t.invalid, class: "tok-invalid" },
|
||||
@@ -59,6 +63,7 @@ function familyFor(lang?: string): CodeFamily | null {
|
||||
const parser = parserFor(lang)
|
||||
if (parser === cppLanguage.parser) return "c"
|
||||
if (parser === ojPythonLanguage.parser) return "python"
|
||||
if (parser === SQLite.language.parser) return "sql"
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -90,7 +95,8 @@ function walk(
|
||||
const emit = (text: string, classes: string) => {
|
||||
if (family) {
|
||||
const marks = fake.some((r) => pos >= r.from && pos < r.to) ? "" : classes
|
||||
splitMarks(text, classes, marks, family, code[pos - 1] ?? "", put)
|
||||
const after = code.slice(pos + text.length, pos + text.length + 20)
|
||||
splitMarks(text, classes, marks, family, code[pos - 1] ?? "", after, put)
|
||||
} else put(text, classes)
|
||||
pos += text.length
|
||||
}
|
||||
|
||||
@@ -6,10 +6,17 @@ import { codeHighlighter } from "../highlight"
|
||||
/**
|
||||
* 编辑器的外观:亮 / 暗两套只管底色、光标、选区、行号这些「框」,
|
||||
* 代码的颜色两套都是 codeHighlighter 打的 tok-* 类名,跟着 html.dark 在 highlight.css 里切。
|
||||
* 框沿用原来的 smoothy(亮)和 One Dark(暗),只把代码配色换成了 VS Code 的 Light+ / Dark+。
|
||||
* 框的颜色照 VS Code 的默认值(src/vs/editor/common/core/editorColorRegistry.ts 等),
|
||||
* 只有暗色的底色用网站自己的,不用 VS Code 的 #1E1E1E。
|
||||
*
|
||||
* 当前行照 VS Code 是一圈边框不是底色,并且只在编辑器有焦点时画:
|
||||
* 自学一课里有十几个代码块,不然每块第一行都框着。
|
||||
*/
|
||||
const highlighting = syntaxHighlighting(codeHighlighter)
|
||||
|
||||
// CodeMirror 自带的选区规则带着 > .cm-scroller > .cm-selectionLayer,选择器短了压不过它
|
||||
const focusedSelection = "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground"
|
||||
|
||||
const light = EditorView.theme({
|
||||
"&": {
|
||||
backgroundColor: "#ffffff",
|
||||
@@ -17,16 +24,24 @@ const light = EditorView.theme({
|
||||
},
|
||||
".cm-content": { caretColor: "#000000" },
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: "#000000" },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
|
||||
backgroundColor: "#FFFD0054",
|
||||
".cm-selectionBackground, .cm-content ::selection": { backgroundColor: "#ADD6FF80" },
|
||||
[focusedSelection]: { backgroundColor: "#ADD6FF" },
|
||||
".cm-selectionMatch": { backgroundColor: "#ADD6FF80" },
|
||||
"&.cm-focused .cm-matchingBracket": {
|
||||
backgroundColor: "#0064001a",
|
||||
outline: "1px solid #B9B9B9",
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: "#00000008" },
|
||||
".cm-searchMatch": { backgroundColor: "#EA5C0055" },
|
||||
".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "#A8AC94" },
|
||||
".cm-activeLine": { backgroundColor: "transparent" },
|
||||
"&.cm-focused .cm-activeLine": { boxShadow: "inset 0 0 0 2px #eeeeee" },
|
||||
".cm-gutters": {
|
||||
backgroundColor: "#ffffff",
|
||||
borderRight: "none",
|
||||
color: "#00000070",
|
||||
color: "#237893",
|
||||
},
|
||||
".cm-activeLineGutter": { backgroundColor: "#00000008" },
|
||||
".cm-activeLineGutter": { backgroundColor: "transparent" },
|
||||
"&.cm-focused .cm-activeLineGutter": { color: "#0B216F" },
|
||||
".cm-tooltip": {
|
||||
border: "1px solid #e0e0e0",
|
||||
backgroundColor: "#f8f8f8",
|
||||
@@ -40,11 +55,9 @@ const light = EditorView.theme({
|
||||
})
|
||||
|
||||
const darkBackground = "#26262a",
|
||||
highlightBackground = "#2c313a",
|
||||
background = "#101014", // naive-ui
|
||||
tooltipBackground = "#353a42",
|
||||
stone = "#7d8799",
|
||||
cursor = "#528bff"
|
||||
highlightBackground = "#2c313a"
|
||||
|
||||
const dark = EditorView.theme(
|
||||
{
|
||||
@@ -52,30 +65,29 @@ const dark = EditorView.theme(
|
||||
color: "var(--code-fg)",
|
||||
backgroundColor: background,
|
||||
},
|
||||
".cm-content": { caretColor: cursor },
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
|
||||
backgroundColor: "#3E4451",
|
||||
".cm-content": { caretColor: "#AEAFAD" },
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: "#AEAFAD" },
|
||||
".cm-selectionBackground, .cm-content ::selection": { backgroundColor: "#264F7880" },
|
||||
[focusedSelection]: { backgroundColor: "#264F78" },
|
||||
".cm-selectionMatch": { backgroundColor: "#ADD6FF26" },
|
||||
"&.cm-focused .cm-matchingBracket": {
|
||||
backgroundColor: "#0064001a",
|
||||
outline: "1px solid #888888",
|
||||
},
|
||||
".cm-searchMatch": { backgroundColor: "#EA5C0055" },
|
||||
".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "#515C6A" },
|
||||
".cm-panels": { backgroundColor: darkBackground, color: "var(--code-fg)" },
|
||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "#72a1ff59",
|
||||
outline: "1px solid #457dff",
|
||||
},
|
||||
".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "#6199ff2f" },
|
||||
".cm-activeLine": { backgroundColor: "#6699ff0b" },
|
||||
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
|
||||
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
|
||||
backgroundColor: "#bad0f847",
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: "transparent" },
|
||||
"&.cm-focused .cm-activeLine": { boxShadow: "inset 0 0 0 2px #282828" },
|
||||
".cm-gutters": {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
color: "#858585",
|
||||
border: "none",
|
||||
},
|
||||
".cm-activeLineGutter": { backgroundColor: highlightBackground },
|
||||
".cm-activeLineGutter": { backgroundColor: "transparent" },
|
||||
"&.cm-focused .cm-activeLineGutter": { color: "#C6C6C6" },
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
|
||||
Reference in new issue
Block a user