mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 00:07:50 +00:00
feat(editor): recognize AsciiDoc files with basic highlighting (#731)
Register .adoc/.asciidoc as AsciiDoc in the language catalog (backend registry and in-memory backend) with // line comments, and add a lightweight CodeMirror stream mode for headings, attributes, delimited blocks, emphasis, inline code, URLs and comments. Refs #708. Co-authored-by: Bl0ck154 <36800583+Bl0ck154@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ use std::sync::Arc;
|
||||
|
||||
/// Built-in definitions, embedded at compile time. Add new languages here.
|
||||
const BUILTIN: &[(&str, &str)] = &[
|
||||
("asciidoc", include_str!("../../../languages/asciidoc.toml")),
|
||||
("bat", include_str!("../../../languages/bat.toml")),
|
||||
("c", include_str!("../../../languages/c.toml")),
|
||||
("clojure", include_str!("../../../languages/clojure.toml")),
|
||||
@@ -373,6 +374,7 @@ mod tests {
|
||||
/// `name`s exported by `@codemirror/language-data` or built into `src/editor/core/cm/languages.ts`
|
||||
/// (the UI resolves `ui-mode` against these).
|
||||
const UI_MODES: &[&str] = &[
|
||||
"AsciiDoc",
|
||||
"Batch",
|
||||
"C",
|
||||
"C++",
|
||||
@@ -532,6 +534,12 @@ mod tests {
|
||||
let reg = reg();
|
||||
assert_eq!(detect(®, "src/main.rs"), "rust");
|
||||
assert_eq!(detect(®, "a/b.TS"), "typescript");
|
||||
for path in ["guide.adoc", "guide.asciidoc", "GUIDE.ADOC"] {
|
||||
assert_eq!(detect(®, path), "asciidoc");
|
||||
}
|
||||
let asciidoc = reg.get(&LanguageId::new("asciidoc")).unwrap();
|
||||
assert_eq!(asciidoc.ui_mode.as_deref(), Some("AsciiDoc"));
|
||||
assert_eq!(asciidoc.line_comment.as_deref(), Some("//"));
|
||||
assert_eq!(reg.detect(Path::new("script"), Some("#!/usr/bin/env python3")).as_str(), "python");
|
||||
assert_eq!(detect(®, "README"), "plaintext");
|
||||
assert_eq!(detect(®, "unknown.zzz"), "plaintext");
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
id = "asciidoc"
|
||||
name = "AsciiDoc"
|
||||
extensions = ["adoc", "asciidoc"]
|
||||
line-comment = "//"
|
||||
indent = { tab-size = 2, insert-spaces = true }
|
||||
ui-mode = "AsciiDoc"
|
||||
@@ -119,6 +119,8 @@ const EXT_LANG: Record<string, string> = {
|
||||
go: "go",
|
||||
json: "json",
|
||||
md: "markdown",
|
||||
adoc: "asciidoc",
|
||||
asciidoc: "asciidoc",
|
||||
css: "css",
|
||||
html: "html",
|
||||
yml: "yaml",
|
||||
@@ -218,6 +220,15 @@ const MEM_LANGUAGES: LanguageInfo[] = [
|
||||
blockComment: null,
|
||||
indent: { tabSize: 2, insertSpaces: true },
|
||||
},
|
||||
{
|
||||
id: "asciidoc",
|
||||
name: "AsciiDoc",
|
||||
extensions: ["adoc", "asciidoc"],
|
||||
uiMode: "AsciiDoc",
|
||||
lineComment: "//",
|
||||
blockComment: null,
|
||||
indent: { tabSize: 2, insertSpaces: true },
|
||||
},
|
||||
{
|
||||
id: "markdown",
|
||||
name: "Markdown",
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { StreamLanguage, type StreamParser } from "@codemirror/language";
|
||||
|
||||
type State = { block: string | null };
|
||||
|
||||
const parser: StreamParser<State> = {
|
||||
startState: () => ({ block: null }),
|
||||
copyState: ({ block }) => ({ block }),
|
||||
token(stream, state) {
|
||||
if (stream.sol()) {
|
||||
const line = stream.string;
|
||||
const delimiter = line.trim();
|
||||
if (state.block) {
|
||||
if (delimiter === state.block) {
|
||||
state.block = null;
|
||||
stream.skipToEnd();
|
||||
return "meta";
|
||||
}
|
||||
stream.skipToEnd();
|
||||
return "monospace";
|
||||
}
|
||||
if (/^={1,6}\s+\S/.test(line)) {
|
||||
stream.skipToEnd();
|
||||
return "heading";
|
||||
}
|
||||
if (/^:[\w-]+:/.test(line)) {
|
||||
stream.skipToEnd();
|
||||
return "attributeName";
|
||||
}
|
||||
if (["----", "....", "____", "++++"].includes(delimiter)) {
|
||||
state.block = delimiter;
|
||||
stream.skipToEnd();
|
||||
return "meta";
|
||||
}
|
||||
if (/^\s*\/\//.test(line)) {
|
||||
stream.skipToEnd();
|
||||
return "comment";
|
||||
}
|
||||
}
|
||||
if (stream.eatSpace()) return null;
|
||||
if (stream.match(/^https?:\/\/[^\s[\]<>]+/)) return "link";
|
||||
if (stream.match(/^\*[^*\n]+\*/)) return "strong";
|
||||
if (stream.match(/^_[^_\n]+_/)) return "emphasis";
|
||||
if (stream.match(/^\x60[^\x60\n]+\x60/)) return "monospace";
|
||||
stream.next();
|
||||
return null;
|
||||
},
|
||||
languageData: { commentTokens: { line: "//" } },
|
||||
};
|
||||
|
||||
export const asciidocLanguage = StreamLanguage.define(parser);
|
||||
@@ -3,6 +3,54 @@ import { highlightTree, tagHighlighter, tags as t } from "@lezer/highlight";
|
||||
import { findLanguageDescription, loadSupport } from "./languages";
|
||||
|
||||
describe("language modes", () => {
|
||||
it("recognizes AsciiDoc files and loads their lightweight syntax mode", async () => {
|
||||
for (const path of ["/docs/guide.adoc", "/docs/guide.asciidoc"]) {
|
||||
const description = findLanguageDescription("AsciiDoc", path);
|
||||
expect(description?.name).toBe("AsciiDoc");
|
||||
const support = await loadSupport(description!);
|
||||
expect(support?.language).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("assigns highlight tags to AsciiDoc syntax", async () => {
|
||||
const support = await loadSupport(findLanguageDescription("AsciiDoc", "/docs/guide.adoc")!);
|
||||
const doc = [
|
||||
"= Guide",
|
||||
":toc: left",
|
||||
"----",
|
||||
"const example = true;",
|
||||
"----",
|
||||
"*bold* _italic_ `code` https://example.com",
|
||||
"// Comment",
|
||||
].join("\n");
|
||||
const highlighter = tagHighlighter([
|
||||
{ tag: t.heading, class: "heading" },
|
||||
{ tag: t.attributeName, class: "attribute" },
|
||||
{ tag: t.meta, class: "meta" },
|
||||
{ tag: t.monospace, class: "monospace" },
|
||||
{ tag: t.strong, class: "strong" },
|
||||
{ tag: t.emphasis, class: "emphasis" },
|
||||
{ tag: t.link, class: "link" },
|
||||
{ tag: t.comment, class: "comment" },
|
||||
]);
|
||||
const highlighted: [string, string][] = [];
|
||||
highlightTree(support!.language.parser.parse(doc), highlighter, (from, to, style) => {
|
||||
highlighted.push([doc.slice(from, to), style]);
|
||||
});
|
||||
expect(highlighted).toEqual([
|
||||
["= Guide", "heading"],
|
||||
[":toc: left", "attribute"],
|
||||
["----", "meta"],
|
||||
["const example = true;", "monospace"],
|
||||
["----", "meta"],
|
||||
["*bold*", "strong"],
|
||||
["_italic_", "emphasis"],
|
||||
["`code`", "monospace"],
|
||||
["https://example.com", "link"],
|
||||
["// Comment", "comment"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("styles `?.` like `.` in JavaScript and TypeScript", async () => {
|
||||
const highlighter = tagHighlighter([{ tag: t.derefOperator, class: "deref" }]);
|
||||
for (const [mode, path] of [
|
||||
|
||||
@@ -68,6 +68,11 @@ const OWN_MODES = [
|
||||
filename: /^(?:CMakeLists\.txt|.*\.cmake(?:\.in)?)$/,
|
||||
load: async () => new LanguageSupport((await import("./cmake")).cmakeLanguage),
|
||||
}),
|
||||
LanguageDescription.of({
|
||||
name: "AsciiDoc",
|
||||
extensions: ["adoc", "asciidoc"],
|
||||
load: () => import("./asciidoc").then((m) => new LanguageSupport(m.asciidocLanguage)),
|
||||
}),
|
||||
LanguageDescription.of({
|
||||
name: "Shell",
|
||||
load: async () => {
|
||||
@@ -245,7 +250,7 @@ const DEDENT_STATEMENT = /^\s*(?:pass|break|continue|return)\b/;
|
||||
const KEYWORD_OPEN_LANGS = new Set(["elixir", "ruby", "lua"]);
|
||||
const KEYWORD_OPEN =
|
||||
/\b(?:do(?:\s*\|[^|]*\|)?|then|->)$|^\s*(?:def|defp|defmodule|class|module|function|local\s+function|if|unless|elsif|elseif|else|while|until|for|case|begin|cond|with|receive|try)\b/;
|
||||
const NO_INDENT_LANGS = new Set(["plaintext", "markdown", "log", "diff", "ignore", "gitattributes"]);
|
||||
const NO_INDENT_LANGS = new Set(["plaintext", "markdown", "asciidoc", "log", "diff", "ignore", "gitattributes"]);
|
||||
|
||||
function opensBlock(code: string, language: string | undefined): boolean {
|
||||
if (!code) return false;
|
||||
|
||||
Reference in New Issue
Block a user