feat: SQL 代码编辑器中文自动补全

- 新增 sql.ts 中文补全列表(关键字 + 常用函数,SQLite 方言)
- enhanceCompletion 接入 SQL,移除原来的早退逻辑
- CodeEditor.vue 补上 lang-sql 语言扩展,与 SyncCodeEditor 一致

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 09:58:52 -06:00
parent ad28c0ba21
commit 47281029ec
3 changed files with 415 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { cpp } from "@codemirror/lang-cpp"
import { python } from "@codemirror/lang-python"
import { sql, SQLite } from "@codemirror/lang-sql"
import { bracketMatching } from "@codemirror/language"
import { Codemirror } from "vue-codemirror"
import {
@@ -34,6 +35,8 @@ const code = defineModel<string>("value")
const isDark = useDark()
const langExtension = computed(() => {
if (language === "SQL")
return sql({ dialect: SQLite, upperCaseKeywords: true })
return ["Python2", "Python3"].includes(language) ? python() : cpp()
})

View File

@@ -8,6 +8,7 @@ import type { EditorView } from "@codemirror/view"
import type { LANGUAGE } from "utils/types"
import { c } from "./c"
import { python } from "./python"
import { sql } from "./sql"
type ChineseCompletion = Pick<
Completion,
@@ -18,6 +19,7 @@ type ChineseCompletion = Pick<
const chineseAnnotations: Record<string, ChineseCompletion[]> = {
python,
c,
sql,
}
export function enhanceCompletion(language: LANGUAGE): CompletionSource {
@@ -27,10 +29,12 @@ export function enhanceCompletion(language: LANGUAGE): CompletionSource {
const word = context.matchBefore(/\w+/)
if (!word && !context.explicit) return null
// SQL 没有中文注释提示,关键字补全由 @codemirror/lang-sql 提供
if (language === "SQL") return null
const trulyLanguage = language.startsWith("Python") ? "python" : "c"
const trulyLanguage =
language === "SQL"
? "sql"
: language.startsWith("Python")
? "python"
: "c"
const completions: Completion[] = (
chineseAnnotations[trulyLanguage] || []
).map((completion) => {

View File

@@ -0,0 +1,404 @@
export const sql = [
{
label: "SELECT",
detail: "查询数据",
type: "keyword",
info: "从表中查询数据,后面接要查询的列名,用 * 表示所有列。",
boost: 100,
apply: "SELECT ",
},
{
label: "FROM",
detail: "指定表",
type: "keyword",
info: "指定要查询的表,和 SELECT 搭配使用,如 SELECT * FROM 表名。",
boost: 98,
apply: "FROM ",
},
{
label: "WHERE",
detail: "筛选条件",
type: "keyword",
info: "按条件筛选行,只保留满足条件的数据,如 WHERE age > 18。",
boost: 96,
apply: "WHERE ",
},
{
label: "ORDER BY",
detail: "排序",
type: "keyword",
info: "按指定列排序默认从小到大ASC加 DESC 表示从大到小。",
boost: 90,
apply: "ORDER BY ",
},
{
label: "GROUP BY",
detail: "分组",
type: "keyword",
info: "按指定列分组,常配合 COUNT、SUM 等聚合函数统计每组数据。",
boost: 88,
apply: "GROUP BY ",
},
{
label: "HAVING",
detail: "分组后筛选",
type: "keyword",
info: "对分组后的结果再筛选WHERE 筛选行HAVING 筛选组。",
boost: 86,
apply: "HAVING ",
},
{
label: "LIMIT",
detail: "限制条数",
type: "keyword",
info: "限制返回的行数,如 LIMIT 5 只取前 5 条,常和 ORDER BY 搭配。",
boost: 84,
apply: "LIMIT ",
},
{
label: "DISTINCT",
detail: "去重",
type: "keyword",
info: "去掉查询结果中的重复值,如 SELECT DISTINCT city FROM users。",
boost: 82,
apply: "DISTINCT ",
},
{
label: "AS",
detail: "起别名",
type: "keyword",
info: "给列或表起别名,让结果更易读,如 SELECT name AS 姓名。",
boost: 80,
apply: "AS ",
},
{
label: "JOIN",
detail: "连接表",
type: "keyword",
info: "把两张表按条件连接起来查询,需要用 ON 指定连接条件。",
boost: 78,
apply: "JOIN ",
},
{
label: "LEFT JOIN",
detail: "左连接",
type: "keyword",
info: "以左表为主连接右表,左表的行都保留,右表没匹配的补 NULL。",
boost: 76,
apply: "LEFT JOIN ",
},
{
label: "ON",
detail: "连接条件",
type: "keyword",
info: "指定两张表的连接条件,如 ON a.id = b.user_id和 JOIN 搭配。",
boost: 74,
apply: "ON ",
},
{
label: "INSERT INTO",
detail: "插入数据",
type: "keyword",
info: "向表中插入新行,如 INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2)。",
boost: 72,
apply: "INSERT INTO ",
},
{
label: "VALUES",
detail: "插入的值",
type: "keyword",
info: "和 INSERT INTO 搭配,写具体要插入的值,顺序要和列名对应。",
boost: 70,
apply: "VALUES ",
},
{
label: "UPDATE",
detail: "更新数据",
type: "keyword",
info: "修改表中已有的数据,配合 SET 设置新值,别忘了加 WHERE 限定范围。",
boost: 68,
apply: "UPDATE ",
},
{
label: "SET",
detail: "设置新值",
type: "keyword",
info: "和 UPDATE 搭配,指定要修改的列和新值,如 SET score = 90。",
boost: 66,
apply: "SET ",
},
{
label: "DELETE FROM",
detail: "删除数据",
type: "keyword",
info: "删除表中的行,一定要配合 WHERE 使用,否则会删掉整张表的数据。",
boost: 64,
apply: "DELETE FROM ",
},
{
label: "CREATE TABLE",
detail: "创建表",
type: "keyword",
info: "新建一张表,需要定义列名和类型,如 CREATE TABLE users (id INTEGER, name TEXT)。",
boost: 62,
apply: "CREATE TABLE ",
},
{
label: "DROP TABLE",
detail: "删除表",
type: "keyword",
info: "删除整张表(包括结构和数据),操作不可恢复,要谨慎使用。",
boost: 60,
apply: "DROP TABLE ",
},
{
label: "ALTER TABLE",
detail: "修改表结构",
type: "keyword",
info: "修改已有表的结构比如添加列ALTER TABLE 表名 ADD COLUMN 列名 类型。",
boost: 58,
apply: "ALTER TABLE ",
},
{
label: "AND",
detail: "并且",
type: "keyword",
info: "连接多个条件,全部成立才算满足,如 WHERE age > 18 AND city = '上海'。",
boost: 56,
apply: "AND ",
},
{
label: "OR",
detail: "或者",
type: "keyword",
info: "连接多个条件,任意一个成立就算满足。",
boost: 54,
apply: "OR ",
},
{
label: "NOT",
detail: "取反",
type: "keyword",
info: "对条件取反,如 NOT IN、NOT LIKE、IS NOT NULL。",
boost: 52,
apply: "NOT ",
},
{
label: "IN",
detail: "在列表中",
type: "keyword",
info: "判断值是否在给定列表中,如 WHERE city IN ('北京', '上海')。",
boost: 50,
apply: "IN ",
},
{
label: "BETWEEN",
detail: "在区间内",
type: "keyword",
info: "判断值是否在某个范围内(包含两端),如 BETWEEN 60 AND 100。",
boost: 48,
apply: "BETWEEN ",
},
{
label: "LIKE",
detail: "模糊匹配",
type: "keyword",
info: "模糊查询,% 匹配任意多个字符_ 匹配单个字符,如 LIKE '张%'。",
boost: 46,
apply: "LIKE ",
},
{
label: "IS NULL",
detail: "是否为空",
type: "keyword",
info: "判断值是否为 NULL空值不能写 = NULL要用 IS NULL。",
boost: 44,
},
{
label: "IS NOT NULL",
detail: "是否非空",
type: "keyword",
info: "判断值不为 NULL常用于过滤掉缺失数据的行。",
boost: 42,
},
{
label: "ASC",
detail: "升序",
type: "keyword",
info: "排序时从小到大排列,是 ORDER BY 的默认方式,可以省略。",
boost: 40,
},
{
label: "DESC",
detail: "降序",
type: "keyword",
info: "排序时从大到小排列,如 ORDER BY score DESC 按分数从高到低。",
boost: 41,
},
{
label: "UNION",
detail: "合并结果",
type: "keyword",
info: "合并两个查询的结果并去重,两个查询的列数和类型要一致。",
boost: 38,
apply: "UNION ",
},
{
label: "CASE",
detail: "条件表达式",
type: "keyword",
info: "类似 if/else 的条件判断,语法 CASE WHEN 条件 THEN 值 ELSE 值 END。",
boost: 36,
apply: "CASE ",
},
{
label: "WHEN",
detail: "当条件成立",
type: "keyword",
info: "和 CASE 搭配,写判断条件,成立时返回 THEN 后面的值。",
boost: 34,
apply: "WHEN ",
},
{
label: "THEN",
detail: "返回值",
type: "keyword",
info: "和 WHEN 搭配,条件成立时返回的结果。",
boost: 32,
apply: "THEN ",
},
{
label: "ELSE",
detail: "否则",
type: "keyword",
info: "CASE 中所有 WHEN 都不成立时返回的默认值。",
boost: 30,
apply: "ELSE ",
},
{
label: "END",
detail: "结束 CASE",
type: "keyword",
info: "标记 CASE 表达式的结束,写 CASE 时不要漏掉。",
boost: 28,
},
{
label: "NULL",
detail: "空值",
type: "keyword",
info: "表示没有值(缺失),判断时要用 IS NULL / IS NOT NULL。",
boost: 26,
},
{
label: "COUNT",
detail: "统计行数",
type: "function",
info: "统计行数COUNT(*) 统计所有行COUNT(列名) 不统计 NULL。",
boost: 92,
apply: "COUNT()",
},
{
label: "SUM",
detail: "求和",
type: "function",
info: "对某一列求和,只能用于数字列,如 SUM(score)。",
boost: 87,
apply: "SUM()",
},
{
label: "AVG",
detail: "平均值",
type: "function",
info: "计算某一列的平均值,会自动忽略 NULL如 AVG(score)。",
boost: 85,
apply: "AVG()",
},
{
label: "MAX",
detail: "最大值",
type: "function",
info: "求某一列的最大值,如 MAX(score) 找最高分。",
boost: 83,
apply: "MAX()",
},
{
label: "MIN",
detail: "最小值",
type: "function",
info: "求某一列的最小值,如 MIN(score) 找最低分。",
boost: 81,
apply: "MIN()",
},
{
label: "LENGTH",
detail: "字符串长度",
type: "function",
info: "返回字符串的字符个数,如 LENGTH(name)。",
boost: 79,
apply: "LENGTH()",
},
{
label: "UPPER",
detail: "转大写",
type: "function",
info: "把字符串中的字母转成大写,如 UPPER('abc') 得到 'ABC'。",
boost: 77,
apply: "UPPER()",
},
{
label: "LOWER",
detail: "转小写",
type: "function",
info: "把字符串中的字母转成小写,如 LOWER('ABC') 得到 'abc'。",
boost: 75,
apply: "LOWER()",
},
{
label: "SUBSTR",
detail: "截取子串",
type: "function",
info: "截取字符串的一部分,语法 SUBSTR(字符串, 起始位置, 长度),位置从 1 开始。",
boost: 73,
apply: "SUBSTR()",
},
{
label: "REPLACE",
detail: "替换字符串",
type: "function",
info: "把字符串中的内容替换成新内容,语法 REPLACE(字符串, 旧内容, 新内容)。",
boost: 71,
apply: "REPLACE()",
},
{
label: "ROUND",
detail: "四舍五入",
type: "function",
info: "按指定小数位四舍五入,如 ROUND(3.456, 2) 得到 3.46。",
boost: 69,
apply: "ROUND()",
},
{
label: "ABS",
detail: "绝对值",
type: "function",
info: "返回数字的绝对值,把负数变成正数,如 ABS(-5) 得到 5。",
boost: 67,
apply: "ABS()",
},
{
label: "IFNULL",
detail: "空值替代",
type: "function",
info: "如果第一个值是 NULL 就返回第二个值,如 IFNULL(score, 0) 把空分数当 0。",
boost: 65,
apply: "IFNULL()",
},
{
label: "TRIM",
detail: "去首尾空格",
type: "function",
info: "去掉字符串首尾的空格,常用于清理输入数据。",
boost: 63,
apply: "TRIM()",
},
]