本頁由機器翻譯,可能有錯誤。 閱讀英文原文

元素

Editor

Editor 是程式碼編輯器:支援五十多種語言的語法醒目提示、行號、多游標、摺疊、自動換行、尋找與取代、診斷與裝飾,並提供程式碼編輯器慣用的按鍵(移動與複製行、註解、縮排、輸入時自動補上的括號與引號)。它能處理任意長度的檔案:只建立畫面內可見的列,醒目提示隨每次編輯逐行更新,因此長檔案的捲動與輸入就像短檔案一樣流暢。輸入透過平台的輸入機制處理,因此輸入法(拼音、假名、組合鍵)與軟鍵盤的運作方式與一般文字欄位相同。

el-editor.tsx
el-editor.tsxLive
import { mount, signal, Text, View } from "nsh";
import { Input } from "nsh/base";
import { Editor, type EditorDiagnostic, type EditorRef, type EditorSearchResult } from "nsh/base";
import { button, buttonText, desktop, field, Log, Panel, Segmented, size, Split, ui, wide } from "./palette";

const SAMPLES = {
  rust: `/// The n-th Fibonacci number.
fn fib(n: u64) -> u64 {
    match n {
        0 | 1 => n,
        _ => fib(n - 1) + fib(n - 2),
    }
}

fn main() {
    let unused = 0;
    for n in 0..10 {
        println!("fib({n}) = {}", fib(n));
    }
}
`,
  typescript: `type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.r ** 2;
    case "square":
      return shape.side ** 2;
  }
}

console.log(area({ kind: "circle", r: 2 }));
`,
  json: `{
  "name": "my-app",
  "entry": "App.tsx",
  "capabilities": ["network"]
}
`,
} as const;

type Language = keyof typeof SAMPLES;

// A problem to mark in each file, as a linter would report it.
const PROBLEMS: Record<Language, EditorDiagnostic[]> = {
  rust: [{ range: { start: { line: 9, column: 8 }, end: { line: 9, column: 14 } }, severity: "warning", message: "unused variable" }],
  typescript: [],
  json: [],
};

// The text is the editor's own (`defaultValue`), an editor per language so
// each keeps its own undo; the app passes what to find (`search`) and reads
// the caret and the matches from the events.
function App() {
  const language = signal<Language>("rust");
  const wrap = signal<"Scroll" | "Wrap">("Scroll");
  const query = signal("");
  const caret = signal("1:1");
  const found = signal<EditorSearchResult>({ count: 0 });
  const events = signal<string[]>([]);
  const log = (line: string) => events.set([line, ...events()].slice(0, 4));
  let editor: EditorRef | undefined;

  const action = (label: string, onPress: () => void) => (
    <View onPress={onPress} role="button" {...button("secondary", { size: "sm" })}>
      <Text style={buttonText()}>{label}</Text>
    </View>
  );

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>editor</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Click into the code and type; Alt-click adds a caret, Cmd/Ctrl+D the next match" : "Tap into the code and type"}
      </Text>

      <Split
        grow
        width={640}
        inspector={
          <>
            <Segmented label="language" options={["rust", "typescript", "json"] as const} value={language()} onChange={(v) => language.set(v)} />
            <Segmented label="long lines" options={["Scroll", "Wrap"] as const} value={wrap()} onChange={(v) => wrap.set(v)} />
            <Panel title="find">
              <Input
                value={query()}
                onChange={({ value }) => query.set(value)}
                onSubmit={() => editor?.run("findNext")}
                submitBehavior="submit"
                placeholder="fib"
                accessibilityLabel="Find"
                autoCapitalize="none"
                autoCorrect={false}
                {...field({ style: { height: size.controlSm, paddingVertical: 0 } })}
              />
              <Text style={{ fontSize: 13, fontVariant: ["tabular-nums"], color: ui().text }}>
                {query() ? (found().error ?? `${found().current == null ? "–" : found().current! + 1} of ${found().count}`) : "–"}
              </Text>
              <View style={{ flexDirection: "row", gap: 8 }}>
                {action("Next", () => editor?.run("findNext"))}
                {action("Undo", () => editor?.run("undo"))}
              </View>
            </Panel>
            <Panel title="caret">
              <Text style={{ fontSize: 13, fontVariant: ["tabular-nums"], color: ui().text }}>{caret()}</Text>
            </Panel>
            <Log title="events" lines={events()} minHeight={96} />
          </>
        }
      >
        <View style={{ height: wide() ? 380 : 300, ...ui().card, borderRadius: 14, overflow: "hidden" }}>
          {() => {
            const lang = language();
            return (
              <Editor
                ref={(r) => (editor = r)}
                defaultValue={SAMPLES[lang]}
                language={lang}
                softWrap={wrap() === "Wrap"}
                search={{ query: query() }}
                diagnostics={PROBLEMS[lang]}
                accessibilityLabel="Code"
                onChange={({ value }) => log(`onChange: ${value.length} characters`)}
                onSelectionChange={({ selections }) => {
                  const head = selections[selections.length - 1].head;
                  caret.set(`${head.line + 1}:${head.column + 1}${selections.length > 1 ? ` (${selections.length} carets)` : ""}`);
                }}
                onSearchChange={({ result }) => found.set(result)}
                onFocus={() => log("onFocus")}
                onBlur={() => log("onBlur")}
                onLineNumberPress={({ line }) => log(`onLineNumberPress: ${line + 1}`)}
                onContextMenu={(e) => log(`onContextMenu: ${e.position.line + 1}:${e.position.column + 1}`)}
                style={{ flex: 1 }}
              />
            );
          }}
        </View>
      </Split>
    </View>
  );
}

mount(App);

編輯器自行負責行為,並附帶一套以佈景主題 token 建構的預設外觀:佈景主題的 font-mono 以 13pt 字級、20pt 列高顯示,行號使用 muted-foreground,有游標的行後方有一條淡淡的 muted 色帶,選取範圍使用 primary 的淡色調,縮排參考線使用 border,並採用 TextView 程式碼區塊所用的內建語法色彩(含淺色與深色)。每個部分都可以用應用程式的樣式覆寫這些預設值(classes 與 styles)。Rust 以 nsh_base::editor() 建立,TSX 則從 nsh/base 使用 <Editor>。

use nsh_base::editor;
use nsh_core::prelude::*;

const MAIN: &str = "fn main() {\n    println!(\"Hello, world!\");\n}\n";

fn code() -> impl IntoElement {
    let source = signal(SharedString::from(MAIN));
    editor()
        .value(move || Some(source.get()))
        .language("rust")
        .on_change(move |value: &SharedString| source.set(value.clone()))
        .flex_1()
}

編輯器會填滿應用程式提供的方框(flex-1、一個高度),並在內部捲動文字:垂直方向永遠可捲動;當行比方框長時可水平捲動,或使用 softWrap 完全不水平捲動,此時長行會在編輯器的寬度處換行。

#文字內容

  • 受控:傳入 value,並在 onChange(回報整份新文字)中再次設定。應用程式變更 value(載入檔案、格式化工具的輸出)時,會以一次可由復原取消的編輯取代文字,離變更處較遠的游標與摺疊維持原位。
  • 非受控:傳入 defaultValue,並從 onChange 或 ref 的 value() 讀取文字。

換行字元統一為 \n:傳入文字中的 \r\n 與 \r 會變成 \n。

#語言

language 以名稱、別名、副檔名或檔名指定語言,不分大小寫:"rust"、"rs"、"typescript"、"tsx"、"main.go"、"Dockerfile"、"Makefile"。它決定醒目提示、mod+/ 切換的註解、比對與縮排的括號、輸入時自動補上的成對符號、構成單字的字元,以及預設縮排:四個空格,網頁語言(JavaScript、TypeScript、JSON、HTML、CSS、YAML 等)為兩個空格,Go 與 Makefile 則使用 Tab。tabSize 與 hardTabs 可為所有語言設定縮排。其他名稱視為純文字,除非應用程式已註冊該語言(自訂語言)。

支援的語言:Rust, JavaScript, JSX, TypeScript, TSX, JSON, Python, Go, C, C++, C#, Java, Kotlin, Swift, Objective-C, Objective-C++, Ruby, PHP, Lua, Shell, Fish, PowerShell, SQL, HTML, XML, Vue, Svelte, CSS, SCSS, Sass, Less, Markdown, YAML, TOML, INI, Dockerfile, Makefile, CMake, Dart, Scala, Elixir, Erlang, Haskell, OCaml, F#, Clojure, Zig, Perl, R, Julia, Nix, Terraform, Protocol Buffers, GraphQL 與 Diff。

<Editor value={source()} onChange={({ value }) => source.set(value)} language={file().name} tabSize={2} softWrap class="flex-1" />

#自訂語言

Rust 程式碼可以讓應用程式的編輯器認識更多語言,並能以自有的醒目提示器(tree-sitter 文法、應用程式自己的解析器)為任何語言醒目提示。register_language 註冊一個 EditorLanguage:包含名稱、別名、副檔名與檔名,以及編輯規則(註解、括號、輸入時自動補上的成對符號、單字字元、縮排),並以 syntax 指定用來醒目提示的內建文法。register_highlighter 為內建或已註冊的語言提供應用程式的醒目提示器:這是一個實作 EditorHighlighter 的型別,它會收到每一次編輯(行、位元組與位置,格式與 tree-sitter 的編輯相同),並在時間預算內、於 UI 執行緒之外,提供每一行的 token(EditorToken,或透過 EditorToken::from_scope 提供 tree-sitter capture 或 TextMate scope)。編輯器會保留這些 token,隨文字編輯而移動它們,並對畫面內的行再次請求,做法與內建醒目提示器相同。language 會優先尋找已註冊的語言,依名稱、別名、副檔名或路徑比對,編輯器與差異檢視皆然。

每個引擎各自保存自己的語言。Rust 應用程式在掛載編輯器之前,於其根元件中註冊;TSX 應用程式則由應用程式的 crate 在其 UI set 的 setup 中註冊,執行該應用程式的每個引擎都會先呼叫它。

use nsh_base::editor::{EditorLanguage, register_highlighter, register_language};
use nsh_core::prelude::*;

static NIMBLE: EditorLanguage = EditorLanguage::new("Nimble")
    .extensions(&["nb"])
    .line_comment("#")
    .tab_size(2);

#[native_module("my-app/native")]
fn native(registry: &mut Registry) {
    registry.setup(|| {
        register_language(&NIMBLE);
        register_highlighter("nimble", || Box::new(NimbleHighlighter::default()));
    });
}
<Editor value={source()} onChange={({ value }) => source.set(value)} language="build.nb" class="flex-1" />

#編輯

編輯器的運作方式與一般程式碼編輯器相同:

  • 多游標。 Alt-click(Option-click)新增游標,mod+d 選取目前選取內容的下一個出現處,mod+shift+l 選取所有出現處,新增游標的按鍵則在上一列或下一列新增一個。輸入、刪除、移動與每個命令都會作用在每個游標上;Escape 回到單一游標。undoSelection(cmd+u、ctrl+u)回到上一次移動之前的選取範圍。
  • 選取範圍擴大:以 expandSelection(ctrl+shift+cmd+right、shift+alt+right)逐級擴大:單字、字串內容與整個字串、括號對的內容與整個括號對、整行、全部文字;shrinkSelection 以相同層級退回。splitSelectionIntoLines(shift+alt+i)把跨越多行的選取範圍變成每行一個選取範圍。
  • 縮排。 新的一行會沿用該行的縮排,並在左括號之後增加一層(Python 的 :、Ruby 的 do 等);在一對括號之間按 Enter,會把右括號放到獨立的一行。Tab 與 Shift+Tab 對選取的行增加與減少縮排;在行首空白中按 Backspace 會退到前一個縮排停駐點。autoIndent 可關閉此行為。
  • 括號與引號在輸入時自動補上,可包住選取內容,再次輸入右括號或右引號時會跳過已存在的那一個(autoClose)。游標處的括號與其配對的括號會以外框標示。
  • 復原會一次取消整段輸入:一個單字、一次刪除、一個命令。移動或按下滑鼠會開始新的一步。
  • 摺疊。 後面接著縮排更深的行的那一行,可以用邊欄中的標記把它們摺疊起來:⌄ 摺疊,› 與該行之後的 ⋯ 展開。游標移入摺疊區時會將其展開(Folding)。
  • 按壓操作。 使用指標裝置時,按一下放置游標,按兩下選取單字,按三下選取整行,Shift-click 延伸選取範圍,拖曳則依所按下時選取的單位來選取。Alt+Shift 拖曳(Option+Shift;Linux 上也可用 Control+Alt)選取矩形區塊:每列一個選取範圍,欄位相同,且超出較短行的末端時仍會延續。在觸控螢幕上,輕點放置游標,輕點兩下或長按選取手指下的單字,選取範圍兩端的控制把手可隨手指拖曳移動各端(把手周圍的文字仍可捲動)。
  • 剪貼簿。 沒有選取內容時,複製與剪下會取走游標所在的整行,貼上時這些內容會以整行的形式貼到游標所在行的上方。

readOnly 讓文字在仍被選取、搜尋與複製時維持原樣。

#按鍵

編輯器的按鍵在取得焦點時才生效;其他輸入元件保有各自的按鍵。mod 在 macOS 與 iOS 上是 Command,其他平台上是 Control(keymap 的按鍵組合)。

按鍵(macOS)按鍵(Windows、Linux)命令
方向鍵,搭配 Shift 選取方向鍵,搭配 Shift 選取以字元與列為單位移動
alt+left / alt+rightctrl+left / ctrl+right以單字為單位移動
cmd+left / cmd+right、home / end、ctrl+a / ctrl+ehome / end該行第一個非空白字元(再按一次則到行首)/ 行尾
cmd+up / cmd+downctrl+home / ctrl+end文字開頭 / 結尾
pageup / pagedownpageup / pagedown向上 / 向下翻一頁
alt+backspace / alt+deletectrl+backspace / ctrl+delete向左 / 向右刪除一個單字
cmd+backspace / cmd+delete、ctrl+kctrl+shift+backspace / ctrl+shift+delete刪除到行首 / 行尾
cmd+z / cmd+shift+zctrl+z / ctrl+shift+z、ctrl+y復原 / 重做
cmd+actrl+a全選
cmd+lctrl+l選取該行(再按一次:連下一行一起選取)
cmd+dctrl+d選取下一個出現處
cmd+shift+lctrl+shift+l選取所有出現處
cmd+alt+up / cmd+alt+downctrl+alt+up / ctrl+alt+down在上方 / 下方新增游標
escapeescape回到單一選取範圍
alt+up / alt+downalt+up / alt+down向上 / 向下移動行
alt+shift+up / alt+shift+downalt+shift+up / alt+shift+down向上 / 向下複製行
cmd+shift+kctrl+shift+k刪除行
cmd+enter / cmd+shift+enterctrl+enter / ctrl+shift+enter在下方 / 上方插入新行
tab / shift+tab、cmd+] / cmd+[tab / shift+tab、ctrl+] / ctrl+[增加 / 減少縮排
cmd+/ctrl+/切換行註解
cmd+shift+\ctrl+shift+\跳到配對的括號
ctrl+jctrl+j合併行
ctrl+t交換游標前後的字元
ctrl+alt+left / ctrl+alt+right,搭配 Shift 選取以單字的一部分為單位移動(camelCase、snake_case)
shift+alt+ashift+alt+a切換區塊註解
shift+alt+ishift+alt+i將選取範圍拆成多行
ctrl+shift+cmd+right / ctrl+shift+cmd+leftshift+alt+right / shift+alt+left擴大 / 縮小選取範圍
cmd+uctrl+u回到上一個選取範圍
cmd+g / cmd+shift+g、f3 / shift+f3f3 / shift+f3尋找下一個 / 上一個符合項目
cmd+e尋找選取內容(onFindWithSelection)
cmd+alt+[ / cmd+alt+]ctrl+shift+[ / ctrl+shift+]摺疊 / 展開游標所在行
f12f12跳到游標處的定義(onDefinitionRequest)
cmd+.ctrl+.對選取範圍執行程式碼動作(onCodeActionRequest)
cmd+c / cmd+x / cmd+vctrl+c / ctrl+x / ctrl+v複製 / 剪下 / 貼上

macOS 與 iOS 另外支援 Cocoa 的文字按鍵:ctrl+b / ctrl+f / ctrl+p / ctrl+n 用於移動,ctrl+h / ctrl+d 用於刪除。

沒有預設按鍵的命令與應用程式自己的命令,使用應用程式以 keymap 繫結的按鍵,並透過 ref 的 run 執行:

import { keymap } from "nsh";

let editor!: EditorRef;

keymap.bind({
  "f9": () => editor.run("sortLinesAscending"),
  "mod+shift+u": () => editor.run("transformToUppercase"),
  "mod+alt+0": () => editor.run("foldAll"),
  "mod+alt+j": () => editor.run("unfoldAll"),
});

#尋找與取代

search 是要尋找的內容:{ query, caseSensitive?, wholeWord?, regex?, inSelection? }。符合的項目會被醒目提示,選取範圍所在的那一個以更強的方式顯示,onSearchChange 回報 { result },也就是一個 { count, current, error }:符合項目的數量、選取範圍位於第幾個(從 0 起算),以及正規表達式無效的原因。findNext 與 findPrevious 選取下一個與上一個符合項目,並會循環;replace(text) 取代目前的符合項目並選取下一個,replaceAll(text) 則以一次編輯取代所有符合項目,並傳回 { count },即取代的數量。正規表達式的取代內容以 $1 與 ${name} 引用其群組。

inSelection 只在開啟時所選取的範圍內尋找(沒有選取時則為游標所在的行):只要保持開啟,這個範圍就會持續存在,即使經過編輯,或選取範圍在符合項目之間移動也一樣。findWithSelection(macOS 上為 cmd+e)以 onFindWithSelection 回報選取的文字,或游標處的單字,供尋找列作為查詢內容。

尋找列由應用程式自行提供:

import { signal, Text, View } from "nsh";
import { Button, Editor, type EditorRef, type EditorSearchResult, Input } from "nsh/base";

function Find(props: { source: string; onChange: (value: string) => void }) {
  let editor!: EditorRef;
  const query = signal("");
  const replacement = signal("");
  const result = signal<EditorSearchResult>({ count: 0 });
  const status = () =>
    result().error ?? (result().current != null ? `${result().current! + 1} of ${result().count}` : `${result().count} found`);
  return (
    <View class="flex-1">
      <View class="flex-row items-center gap-2 border-b border-border p-2">
        <Input value={query()} onChange={({ value }) => query.set(value)} onSubmit={() => editor.run("findNext")} placeholder="Find" class="flex-1" />
        <Text class="text-sm text-muted-foreground">{status()}</Text>
        <Button onPress={() => editor.run("findPrevious")} class="px-2"><Text>↑</Text></Button>
        <Button onPress={() => editor.run("findNext")} class="px-2"><Text>↓</Text></Button>
        <Input value={replacement()} onChange={({ value }) => replacement.set(value)} placeholder="Replace" class="flex-1" />
        <Button onPress={() => editor.replace(replacement())} class="px-2"><Text>Replace</Text></Button>
        <Button onPress={async () => console.log("replaced", (await editor.replaceAll(replacement())).count)} class="px-2"><Text>All</Text></Button>
      </View>
      <Editor
        ref={editor}
        value={props.source}
        onChange={(e) => props.onChange(e.value)}
        language="ts"
        search={query() ? { query: query() } : undefined}
        onSearchChange={(e) => result.set(e.result)}
        class="flex-1"
      />
    </View>
  );
}

#診斷

diagnostics 標示語言伺服器或 linter 回報的問題:在每個範圍下方,以對應嚴重程度的顏色(error、warning、info、hint)畫上細波浪底線。位置為從 0 起算的行與 UTF-16 欄,與語言伺服器相同,因此其診斷可以原樣傳入。使用者輸入時,標記會留在原本的文字上,直到應用程式傳入新的標記;顯示訊息(清單、行上的懸浮卡片)則由應用程式負責。

診斷也帶有語言伺服器 Diagnostic 的其餘欄位。其 tags 的文字繪製方式與語言用戶端相同:unnecessary 程式碼(未使用的變數、無法到達的程式碼)會變淡,deprecated 程式碼會加上刪除線,分別位於 unnecessaryText 與 deprecatedText 部分;帶有標籤的提示只會顯示其文字本身。codeDescription.href 是說明其代碼(例如 lint 規則)的頁面,relatedInformation 列出其他說明該診斷的位置({ location: { uri?, range }, message }):懸浮時會把這兩者交給應用程式,由應用程式在其懸浮卡片中顯示並開啟該頁面。語言伺服器以數字傳送標籤(1、2);編輯器則使用其名稱。

const problems = signal<EditorDiagnostic[]>([]);

async function lint(text: string) {
  // [{ range, severity, message, source?, code?, codeDescription?, tags?, relatedInformation? }]
  problems.set(await check(text));
}

<Editor
  value={source()}
  onChange={({ value }) => {
    source.set(value);
    lint(value);
  }}
  language="ts"
  diagnostics={problems()}
  classes={{ diagnosticWarning: "bg-orange-500", unnecessaryText: "text-zinc-400" }}
  class="flex-1"
/>

#裝飾

decorations 為應用程式選定的範圍設定樣式:來自語言伺服器的語意醒目提示、正在執行的陳述式、審查中變更過的行。裝飾的 class 與 style 為其文字設定樣式;lineClass 與 lineStyle 為其所在行的列設定樣式,位於文字後方。與診斷一樣,它們在編輯後仍會留在原本的文字上,直到應用程式設定新的裝飾。

const range = (line: number, start: number, end: number) => ({ start: { line, column: start }, end: { line, column: end } });

<Editor
  value={source()}
  language="sql"
  readOnly
  decorations={[
    { range: range(4, 0, 0), lineClass: "bg-green-500/10" },
    { range: range(7, 6, 18), class: "underline text-blue-600" },
  ]}
  class="flex-1"
/>

#邊欄與概覽尺規標記

裝飾的 gutter 會在行號前的欄帶中,於其範圍涵蓋的每一行旁放置標記:版本控制差異新增或修改的行,或中斷點(空範圍涵蓋其所在行,如同 lineClass)。搭配 placement: "before" 時,則是在範圍起點之前的邊界上橫跨欄帶畫一條線,表示該處有行被刪除。欄帶寬 gutterDecorationsWidth 點(預設為 3),只要有裝飾帶有邊欄標記就會保留,因此標記出現或消失時文字不會移位。位於摺疊內的範圍會標記在摺疊所在的列。

裝飾的 overviewRuler 會在概覽尺規上放置標記。概覽尺規是編輯器右緣的欄帶,寬 14 點(與捲軸同寬;overviewRuler 部分可設定其他寬度),將整段文字對應到其高度,如同 Monaco 的做法。標記至少 6 點高,並佔尺規的一個 lane:left、center(預設)、right 或 full。只要有裝飾在尺規上有標記,尺規就會顯示。

每個標記採用應用程式的 class 與 style,沒有它們時以前景色繪製。兩者都會如同裝飾的範圍一樣跟隨編輯;刪除已標記範圍的編輯會在原處留下一個空範圍。

<Editor
  value={source()}
  onChange={({ value }) => source.set(value)}
  language="ts"
  decorations={[
    { range: range(2, 0, 0), gutter: { class: "bg-yellow-500" }, overviewRuler: { lane: "left", class: "bg-yellow-500" } },
    { range: { start: { line: 3, column: 0 }, end: { line: 5, column: 0 } }, gutter: { class: "bg-green-500" } },
    { range: range(6, 0, 0), gutter: { class: "bg-red-500", placement: "before" }, overviewRuler: { lane: "left", class: "bg-red-500" } },
  ]}
  class="flex-1"
/>

#自訂語法醒目提示

裝飾的 token 會讓其文字以內建醒目提示為該類 token 所使用的顏色著色(comment、keyword、string、number、title、attr、built、deletion),取代內建醒目提示器在該處找到的結果:應用程式自己的醒目提示器(語言伺服器的語意 token、自行撰寫的剖析器)把它的 token 對應到這些種類,就能在淺色與深色模式下得到編輯器的顏色。最內層裝飾的 token 優先;class 與 style 會套用在其上。highlighting={false} 會關閉內建醒目提示器,使文字只由應用程式的 token 著色,而 language 仍決定註解、括號與縮排。

const kinds: Record<string, EditorToken> = { keyword: "keyword", function: "title", string: "string", type: "built" };

<Editor
  value={source()}
  onChange={({ value }) => source.set(value)}
  language="ts"
  highlighting={false}
  decorations={semanticTokens().map((t) => ({ range: range(t.line, t.start, t.end), token: kinds[t.type] }))}
  class="flex-1"
/>

#摺疊

摺疊從縮排比前一行更深的行開始。foldRanges 改為提供編輯器應用程式自己的範圍({ startLine, endLine },即語言伺服器摺疊範圍的形式,會原樣傳入):第 startLine 行顯示摺疊,其後直到 endLine 的行會被摺起。它們會在編輯過程中保持在原來的行上,直到應用程式傳入新的範圍。

摺疊可以巢狀:摺疊一個區塊時,其內部的摺疊仍保持摺疊,因此展開該區塊時會顯示它們原本的樣子。foldRecursive 會摺疊一個區塊及其內所有區塊,unfoldRecursive 會全部展開,foldLevel(n) 會摺疊巢狀層級 n 的每個區塊,foldSelection(或 foldRange(range))會摺疊任意行,不論摺疊從何處開始。onFoldChange 會在每次變更後回報已摺疊的範圍,folds() / setFolds(ranges) 則讀取並還原它們,以便與檔案一併保存。

const ranges = signal<EditorFoldRange[]>([]);
const folded = new Map<string, EditorFoldRange[]>();

<Editor
  ref={editor}
  value={file().text}
  language={file().name}
  foldRanges={ranges()} // from the language server's textDocument/foldingRange
  onFoldChange={({ folds }) => folded.set(file().name, folds)}
  class="flex-1"
/>
<Button onPress={() => editor.foldLevel(2)}><Text>Fold the methods</Text></Button>

#中斷點與內容選單

按下行號會回報 onLineNumberPress({ line }),這是應用程式以裝飾繪製中斷點的位置。次要按鈕(右鍵,Mac 上為 Control 點按)會回報 onContextMenu({ position, x, y }):指標下文字中的位置,以及根視圖中的點,應用程式在此開啟自己的選單。在觸控螢幕上,長按、連點兩下、放開控點以及輕點選取範圍也會回報它,並附帶 selection:選取範圍在根視圖中的方框({ x, y, width, height }),以及位於其上緣中間的 x / y,用於編輯選單。

<Editor
  value={source()}
  onContextMenu={({ x, y, selection }) =>
    Menu.popup(selection ? editItems : contextItems, { x, y })
  }
/>
const breakpoints = signal<number[]>([]);

<Editor
  value={source()}
  language="py"
  onLineNumberPress={({ line }) =>
    breakpoints.set(breakpoints().includes(line) ? breakpoints().filter((l) => l !== line) : [...breakpoints(), line])
  }
  decorations={breakpoints().map((line) => ({ range: range(line, 0, 0), lineClass: "bg-red-500/15" }))}
  class="flex-1"
/>

#語言功能

建議、懸停資訊、跳至定義與程式碼動作來自應用程式的語言伺服器。編輯器自行繪製自動完成清單、行內自動完成與定義連結,以及它們的按鍵;其餘部分則把時機與位置交給應用程式,由應用程式繪製自己的彈出視窗(錨定於 pageBounds 的 Overlay)。

#自動完成

設定 completion 後,編輯器會向應用程式請求建議,並以清單顯示在正在輸入的字詞下方(接近視窗底部時顯示在上方):

  • 在字詞結尾輸入字母、數字或 _ 時,以 onCompletionRequest(event) 請求,觸發為 "typing":event 含有其 id、游標的 offset 與 position、query(目前已輸入的字詞)以及其起點的 queryStart 偏移量。completionTriggerCharacters(["."]、["::", "<"])中的每一個都會在其後以空查詢請求,觸發為 "character"(以及 character),而 showCompletions() 或 run("showCompletions") 會在游標處請求,觸發為 "invoked"。
  • 應用程式以 presentCompletions(event.id, items) 回應。每個項目都是語言伺服器的 CompletionItem 形式:label,以及選用的 kind("function"、"variable"、"class"……)、detail、documentation、insertText、filterText、sortText 與 range。最新請求的回應會顯示;較早的回應,或針對游標已離開之字詞的回應,會被擱置。
  • 繼續輸入會就地篩選清單:查詢字串會以依序出現的字母比對每個項目的 filterText(否則比對其 label),不分大小寫,以查詢字串開頭的字詞優先,其次是最接近的符合,再依 sortText 排序。符合的字母會醒目顯示。傳入 incomplete: true(presentCompletions(id, items, true))可讓編輯器在持續輸入時重新請求。
  • 上下鍵(以及 Control+P / Control+N)在清單中移動,Page Up 與 Page Down 以頁為單位移動,Enter 與 Tab 接受,Escape 關閉。指標會標示一個項目,點按(觸控螢幕上為輕點)即接受。所選項目的 documentation 顯示在清單旁。
  • 接受時,會將項目的 range(否則為查詢字串)替換為其 insertText(否則為其 label),作為可由復原一步取消的單一步驟,在游標處以及每個其前方文字相同的其他游標處執行,並回報 onCompletionAccept({ item })。
  • 當游標離開字詞、輸入空白或其他標點、編輯器失去焦點,以及游標捲出視野時,清單會關閉。

輸入暫停(inlineCompletionDelay,預設 300 毫秒)且沒有顯示清單時,會請求行內自動完成:onInlineCompletionRequest({ id, offset, position })。presentInlineCompletion(id, text) 會以柔和的顏色在游標處顯示 text,Tab 會插入它,Escape、編輯或移動則會清除它。

let editor!: EditorRef;

<Editor
  ref={(r) => (editor = r)}
  value={source()}
  onChange={({ value }) => source.set(value)}
  language="ts"
  completion
  completionTriggerCharacters={["."]}
  onCompletionRequest={async (e) => {
    const items = await server.completion(e.position);
    editor.presentCompletions(e.id, items);
  }}
  onInlineCompletionRequest={async (e) => {
    editor.presentInlineCompletion(e.id, await assistant.suggest(e.offset));
  }}
  class="flex-1"
/>

#懸停與按下

  • onHover(event):滑鼠停在某個字元上且沒有按鈕按下(桌面與網頁;拖曳會選取文字)。event 含有其 position、其 UTF-16 offset、其字詞的 range、指標的 pagePosition、字詞的 pageBounds、按住的 modifiers 以及該處的 diagnostics,應用程式會顯示其訊息。移到另一個字詞會立即回報下一個懸停。
  • onHoverEnd():滑鼠離開了字詞或編輯器,或文字在其下方變更或捲動。
  • onTextPress(event):某個字元被按下,發生在編輯器放置游標之後:{ position, offset, range, pagePosition, modifiers, clicks }。
  • caretBounds() 與 rangeBounds(range):游標與範圍所在之處,{ bounds, pageBounds }(在編輯器與根視圖中):應用程式的彈出視窗會在此開啟。
const hover = signal<EditorHoverEvent | null>(null);

<Editor
  ref={editor}
  value={source()}
  language="ts"
  onHover={(event) => hover.set(event)}
  onHoverEnd={() => hover.set(null)}
  class="flex-1"
/>
<If when={hover()}>
  {(event) => (
    <Overlay anchor={event().pageBounds} placement="top" class="rounded-md border border-border bg-popover p-2">
      <Text>{event().diagnostics.map((d) => d.message).join("\n") || describe(event().range)}</Text>
    </Overlay>
  )}
</If>

#跳至定義與程式碼動作

按住 Mod(macOS 與 iOS 上為 Command,其他平台為 Control)會把滑鼠下的字詞變成連結,如同程式碼編輯器:

  • 按住 Mod 於字詞上時(滑鼠停在該處時按下,或按住時滑鼠移到該處),編輯器以 onDefinitionRequest(event) 請求,觸發為 "hover":event 含有其 id、字元的 position 與 offset,以及字詞的 range。當語言伺服器找到定義時,以 presentDefinitionLink(event.id, range) 回應:該範圍會加上底線(definitionLink 部分),指標在其上會變成手形。放開 Mod、滑鼠離開、文字變更或捲動,或以 hideDefinitionLink(),連結就會消失。最新請求的回應會顯示;較早的回應會被擱置。
  • 在字詞上按住 Mod 點按時,以觸發 "press" 請求;點在連結上時,游標維持原位。F12(goToDefinition、run("goToDefinition"))會針對游標處的字詞請求,觸發為 "key"。應用程式負責前往定義:同一份文字內的定義用 revealPosition 與 setSelections,其他檔案則用應用程式自己的導覽。
  • Mod+.(showCodeActions、run("showCodeActions"))會回報 onCodeActionRequest({ range, position, bounds }):主要選取範圍(起點在前)、游標及其方框。應用程式向其語言伺服器請求動作,在 bounds.pageBounds 顯示自己的選單(Menu 或 Overlay),並以 edit(edits) 套用所選動作的編輯。
let editor!: EditorRef;

<Editor
  ref={(r) => (editor = r)}
  value={source()}
  onChange={({ value }) => source.set(value)}
  language="ts"
  onDefinitionRequest={async (e) => {
    const target = await server.definition(e.position);
    if (!target) return;
    if (e.trigger === "hover") editor.presentDefinitionLink(e.id, target.originRange ?? e.range);
    else editor.revealPosition(target.range.start, true);
  }}
  onCodeActionRequest={async (e) => {
    const actions = await server.codeActions(e.range);
    const picked = await choose(actions, e.bounds?.pageBounds);
    if (picked) editor.edit(picked.edits);
  }}
  class="flex-1"
/>

#大小與捲動

使用 minRows 和/或 maxRows 時,編輯器會隨文字增長:高度等於其列數,至少 minRows 列,從 maxRows 起其文字會捲動,如同訊息方塊或筆記本的儲存格。否則由應用程式決定其大小,而 scrollBeyondLastLine 讓文字可以捲動超過最後一行:最多一個螢幕(true,預設)、完全不行(false)或指定的列數。游標移動時,其周圍會保持 cursorSurroundingLines 列在視野內。將選取範圍拖曳超出編輯器邊緣時會繼續捲動,指標越遠,速度越快。

showWhitespaces 會為每個空白顯示 ·,為每個 Tab 顯示 →(whitespace 部分),rulers 則在其每個欄位畫一條線(ruler 部分),作為一行長度的參考線。

<Editor
  defaultValue={message()}
  language="markdown"
  minRows={2}
  maxRows={8}
  rulers={[72]}
  showWhitespaces
/>

#方法

ref={editor} 提供編輯器的方法(EditorRef),供應用程式的按鈕、選單與按鍵使用。會回傳值的方法(value()、selections()、replaceAll()、canUndo()、caretBounds()、folds()、……)在 JS 中回傳其 Promise,因此要 await 它們:const text = await editor.value()。若方法收到超出文字範圍的行(fold、scrollToLine、goToLine、……),不會變更任何內容,並記錄一則指出該方法與該行的警告。

方法說明
focus()、blur()取得焦點,或放棄焦點。
value()Promise<string>:文字。
setValue(value)取代文字,作為可由復原取消的編輯。
insert(text)在每個選取範圍處輸入 text,照原樣輸入(括號不會自動閉合)。
edit(edits)套用 { range, text } 取代項(目前文字的範圍;與較早者重疊的會被略過),作為可由復原取消的一步;選取範圍會隨周圍的文字移動。程式碼動作與格式化編輯由此處理。
selections()Promise<EditorSelection[]>:每個選取範圍,{ anchor, head },主要(最新)者在最後。
setSelections(selections)設定選取範圍(最後一個為主要),並將主要選取範圍捲動到視野內。
run(command)執行命令,如同其按鍵所觸發(命令)。
replace(text)取代目前的符合項並選取下一個。
replaceAll(text)Promise<EditorReplaceAll>:將每個符合項取代為一次編輯;{ count },數量。
fold(line)、unfold(line)摺疊第 line 行(從 0 起)下方的行,或將其展開。
foldRecursive(line)、unfoldRecursive(line)摺疊第 line 行上的摺疊及其內所有摺疊,或全部展開。
foldLevel(level)摺疊巢狀層級 level(1:最外層)的每個摺疊。
foldRange(range)摺疊 range({ startLine, endLine })的行,不論摺疊從何處開始。
folds()Promise<EditorFoldRange[]>:已摺疊的範圍,包含巢狀者。
setFolds(folds)精確地摺疊 folds。
scrollToLine(line)將第 line 行捲動到視野內,並展開遮住它的摺疊。
canUndo()、canRedo()Promise<boolean>:是否有可復原的編輯,或可重做的編輯。
caretBounds()Promise<EditorBounds | null>:主要游標的方框 { bounds, pageBounds },在編輯器中(已套用其捲動)與在根視圖中;編輯器完成版面配置前為 null。
rangeBounds(range)Promise<EditorBounds[]>:range 的方框,其涵蓋的每一列各一個。
scrollOffset()、setScrollOffset(offset)Promise<Point>:文字已捲動的距離;將其捲動到 { x, y }。
visibleRange()Promise<EditorRange>:視區所顯示的文字,從其第一列的開頭到最後一列的結尾。
selectedText()Promise<string>:主要選取範圍的文字。
offsetToPosition(offset)、positionToOffset(position)Promise<EditorPosition>、Promise<number>:在 value() 的 UTF-16 偏移量與位置之間轉換。
revealPosition(position, center)將 position 捲動到視野內並展開遮住它的摺疊;使用 center 時捲動到中間。
goToLine(line)將游標放在第 line 行(從 0 起)的開頭,並捲動到中間。
showCompletions()在游標處請求自動完成,其前的字詞作為查詢字串(自動完成)。
presentCompletions(id, items, incomplete?)回應自動完成請求 id:清單顯示 items,並依之後輸入的內容篩選;使用 incomplete 時,繼續輸入會再次請求。沒有項目則關閉清單。
hideCompletions()關閉自動完成清單。
presentInlineCompletion(id, text)、hideInlineCompletion()以 text 回應行內自動完成請求 id,顯示在游標處;清除它。
presentDefinitionLink(id, range)、hideDefinitionLink()回應定義請求 id(觸發 "hover"):range 會加上底線作為連結(跳至定義);將其隱藏。
let editor!: EditorRef;

<Button onPress={() => editor.run("toggleComment")}><Text>Comment</Text></Button>
<Button onPress={async () => save(await editor.value())}><Text>Save</Text></Button>
<Button onPress={async () => (await editor.canUndo()) && editor.run("undo")}><Text>Undo</Text></Button>
<Button onPress={() => editor.setSelections([{ anchor: { line: 0, column: 0 }, head: { line: 0, column: 0 } }])}>
  <Text>Go to the top</Text>
</Button>
<Editor ref={editor} value={source()} onChange={({ value }) => source.set(value)} language="rs" class="flex-1" />

#指令

run 依名稱接收編輯器的指令(EditorCommand):

指令作用
moveLeft, moveRight, moveUp, moveDown, moveWordLeft, moveWordRight, moveLineStart, moveLineEnd, movePageUp, movePageDown, moveDocumentStart, moveDocumentEnd移動游標。
selectLeft, selectRight, selectUp, selectDown, selectWordLeft, selectWordRight, selectLineStart, selectLineEnd, selectPageUp, selectPageDown, selectDocumentStart, selectDocumentEnd擴展選取範圍。
moveToMatchingBracket跳到與游標處括號相配對的括號。
selectAll, selectLine, selectWord, selectNextOccurrence, selectPreviousOccurrence, selectAllOccurrences選取。
moveSubwordLeft, moveSubwordRight, selectSubwordLeft, selectSubwordRight依單字的一部分移動或擴展選取範圍(camelCase 駝峰、snake_case 片段)。
moveParagraphUp, moveParagraphDown, selectParagraphUp, selectParagraphDown移動或擴展至段落前或後的空白行。
expandSelection, shrinkSelection依單位擴大選取範圍,或縮回。
undoSelection, redoSelection回到上次移動前的選取範圍,或再次前進。
splitSelectionIntoLines將每個選取範圍的每一行各設為一個選取範圍。
addCaretAbove, addCaretBelow, singleSelection新增游標;回到單一選取範圍。
deleteBackward, deleteForward, deleteWordLeft, deleteWordRight, deleteToLineStart, deleteToLineEnd, deleteLine刪除。
newline, newlineBelow, newlineAbove, indent, outdent, toggleComment, toggleBlockComment行、縮排與註解。
sortLinesAscending, sortLinesDescending排序選取的行(若僅有游標,則為每一行)。
transformToUppercase, transformToLowercase, transformToTitleCase變更選取範圍的大小寫(游標:其所在的單字)。
trimTrailingWhitespace移除每一行結尾的空白字元。
moveLinesUp, moveLinesDown, duplicateLinesUp, duplicateLinesDown, joinLines, transposeCharacters重新排列。
undo, redo復原與重做。
fold, unfold, foldAll, unfoldAll摺疊游標所在行,或全部摺疊。
foldRecursive, unfoldRecursive摺疊游標所在的摺疊區塊及其內所有摺疊區塊,或將其展開。
foldSelection摺疊每個跨越多行的選取範圍的各行。
findNext, findPrevious選取 search 的下一個或上一個符合項目。
findWithSelection將選取的文字回報給尋找列(onFindWithSelection)。
copy, cut, paste剪貼簿。
showCompletions在游標處請求自動完成(自動完成)。
goToDefinition請求游標處單字的定義(onDefinitionRequest,觸發方式 "key")。
showCodeActions請求選取範圍的程式碼動作(onCodeActionRequest)。

#為各部位設定樣式

class 與 style 用於設定編輯器外框的樣式。classes 依部位接收 Tailwind 類別,styles 依部位接收行內樣式;部位的類別會接在其預設樣式之後,因此優先於預設樣式,而其 styles 又優先於類別。字型由 fontFamily、fontSize 與 lineHeight 設定,編輯器以此測量文字。

<Editor
  value={source()}
  onChange={({ value }) => source.set(value)}
  language="tsx"
  fontFamily="JetBrains Mono"
  fontSize={14}
  lineHeight={22}
  class="flex-1 rounded-lg border border-border"
  classes={{
    gutter: "bg-muted",
    lineNumber: "text-xs",
    activeLine: "bg-yellow-500/10",
    selection: "bg-blue-500/30",
  }}
  styles={{ caret: { backgroundColor: "#f59e0b", width: 3 } }}
/>
部位說明預設值
gutter行號與摺疊標記所在的欄行號前 12pt,標記後 4pt
lineNumber每個行號靠右對齊,muted-foreground
activeLineNumber有游標之行的行號,套用於 lineNumber 之後foreground
foldMarker摺疊起始行的標記⌄,摺疊時為 ›,muted-foreground
overviewRuler沿右側邊緣的概覽尺規,當裝飾在該處有標記時顯示無
content邊欄旁的文字區域文字前 4pt
line每一列文字
activeLine位於有游標之行的列後方、捲動指示器下方muted,淡色
selection位於選取文字後方primary 的淡色調
caret游標寬 2pt,foreground,閃爍
composition輸入法正在組字的文字底線
searchMatchsearch 的符合項目半透明琥珀色
currentSearchMatch選取範圍所在的符合項目,套用於 searchMatch 之後較濃的琥珀色
matchingBracket游標處的括號及與其配對的括號1pt 邊框
indentGuide每個縮排層級一條參考線1pt 的 border 線
foldPlaceholder摺疊行在其文字之後顯示的內容⋯,muted-foreground
placeholder文字為空時顯示muted-foreground
diagnosticError錯誤下方的線destructive
diagnosticWarning警告下方的線琥珀色
diagnosticInfo資訊下方的線藍色
diagnosticHint提示下方的線muted-foreground
unnecessaryTextunnecessary 診斷的文字muted-foreground
deprecatedTextdeprecated 診斷的文字刪除線
whitespace空格與 Tab 的標記(showWhitespaces)· 與 →,muted-foreground 60 %
rulerrulers 的線1 point 的 border 線
selectionHandle觸控選取範圍兩端控制把手的圓鈕10pt 的 primary 圓點
selectionHandleStart, selectionHandleEnd起點的圓鈕(位於其行上方)與終點的圓鈕(位於下方),套用於 selectionHandle 之後
completionMenu自動完成清單的外框popover、1pt 的 border、圓角、陰影
completionItem清單中的每個項目一列的高度,8pt 內距
selectedCompletionItem選取的項目,套用於 completionItem 之後accent
completionMatch標籤中與查詢相符的字母半粗體,primary
completionDetail項目的詳細資訊,位於其標籤之後靠右對齊,muted-foreground
completionKind項目的種類,位於其標籤之前簡短名稱(fn、var、cls),muted-foreground
completionDocumentation選取項目的文件,位於清單旁同 completionMenu,寬度最多 320pt
inlineCompletion游標處的行內完成muted-foreground
definitionLink定義連結的底線文字下方 1pt 的 primary 線

#屬性

屬性Rust型別預設值說明
value.value(..)string由應用程式控制的文字(請從 onChange 再次設定)。
defaultValue.default_value(..)string編輯器自行保存文字時的初始文字。
language.language(..)string純文字語言的名稱、別名、副檔名或檔案名稱(語言)。
placeholder.placeholder(..)string文字為空時顯示。
readOnly.read_only(..)booleanfalse文字可選取與複製,但無法變更。
autoFocus.auto_focus(..)booleanfalse出現時取得焦點。
lineNumbers.line_numbers(..)booleantrue邊欄的行號。
softWrap.soft_wrap(..)booleanfalse過長的行會依編輯器寬度換行。
tabSize.tab_size(..)number該語言的預設值一個 Tab 與一個縮排層級的欄數。
hardTabs.hard_tabs(..)boolean該語言的預設值以 Tab 縮排。
folding.folding(..)booleantrue縮排的行可透過邊欄標記摺疊。
foldRanges.fold_ranges(..)EditorFoldRange[]依縮排由應用程式提供的摺疊起點(摺疊)。
highlighting.highlighting(..)booleantrue內建的語法醒目提示(自訂醒目提示)。
autoClose.auto_close(..)booleantrue輸入時自動補上成對的括號與引號。
autoIndent.auto_indent(..)booleantrue換行時保留縮排,並在左括號後縮排。
activeLine.active_line(..)booleantrue醒目提示有游標之行的列。
indentGuides.indent_guides(..)booleantrue縮排參考線。
scrollBeyondLastLine.scroll_beyond_last_line(..)boolean | numbertrue文字可捲動超過最後一行:最多一個畫面(true)、完全不可(false),或指定的列數。
fontFamily.font_family(..)string主題的 font-mono繪製與測量文字所用的字型。
fontSize.font_size(..)number13字型大小,單位為 point。
lineHeight.line_height(..)number20一列的高度,單位為 point。
search.search(..)EditorSearch要尋找的內容(尋找與取代)。
diagnostics.diagnostics(..)EditorDiagnostic[][]要標記的問題(診斷)。
decorations.decorations(..)EditorDecoration[][]由應用程式設定樣式的範圍(裝飾)。
gutterDecorationsWidth.gutter_decorations_width(..)number3邊欄中裝飾標記區帶的寬度,單位為 point。
classes.classes(..)EditorClasses{}依部位指定的 Tailwind 類別。
styles.styles(..)EditorStyles{}依部位指定的行內樣式,優先於類別。
accessibilityLabel.accessibility_label(..)string螢幕閱讀器對編輯器的稱呼。
showWhitespaces.show_whitespaces(..)booleanfalse空格與 Tab 顯示為 · 與 →。
rulers.rulers(..)number[][]要繪製垂直線的欄位。
minRows.min_rows(..)number編輯器隨文字增長,起始為此列數(大小與捲動)。
maxRows.max_rows(..)number編輯器隨文字增長至此列數,之後開始捲動。
cursorSurroundingLines.cursor_surrounding_lines(..)number1游標上下方保持可見的列數。
completion.completion(..)booleanfalse編輯器請求建議並以清單顯示(自動完成)。
completionTriggerCharacters.completion_trigger_characters(..)string[][]輸入時,還有哪些內容會觸發自動完成請求。
inlineCompletionDelay.inline_completion_delay(..)number300輸入暫停多少毫秒後,才請求行內完成。
class, style樣式方法string, StyleProp<ViewStyle>編輯器的外框。

#事件

事件Rust說明
onChange.on_change(|value: &SharedString| ..)文字已變更:{ value },即完整的新文字。
onSelectionChange.on_selection_change(|selections: &Vec<EditorSelection>| ..)選取範圍已變更:{ selections },即全部選取範圍,主要選取範圍在最後。
onFocus, onBlur.on_focus(|| ..), .on_blur(|| ..)編輯器取得或失去焦點。
onSearchChange.on_search_change(|result: &EditorSearchResult| ..)搜尋的符合項目已變更:{ result },一個 { count, current, error }。
onLineNumberPress.on_line_number_press(|line: &u32| ..)按下行號:{ line },即該行,從 0 起算。
onContextMenu.on_context_menu(|event: &EditorContextMenuEvent| ..)在文字上按下次要按鈕,或觸控選取了文字:{ position, x, y, selection? }。
onHover.on_hover(|event: &EditorHoverEvent| ..)滑鼠停留在某個字元上(語言功能)。
onHoverEnd.on_hover_end(|| ..)懸停結束。
onTextPress.on_text_press(|event: &EditorTextPressEvent| ..)按下某個字元:附帶其所在單字、修飾鍵與點擊次數。
onFindWithSelection.on_find_with_selection(|query: &String| ..)findWithSelection 已執行:{ query },即供尋找列查詢使用的文字。
onFoldChange.on_fold_change(|folds: &Vec<EditorFoldRange>| ..)摺疊範圍已變更:{ folds },即全部範圍,包含巢狀範圍。
onCompletionRequest.on_completion_request(|event: &EditorCompletionRequest| ..)編輯器請求自動完成:{ id, offset, position, query, queryStart, trigger, character? }(自動完成)。
onCompletionAccept.on_completion_accept(|event: &EditorCompletionAcceptEvent| ..)已接受並插入項目:{ item }。
onInlineCompletionRequest.on_inline_completion_request(|event: &EditorInlineCompletionRequest| ..)輸入暫停:{ id, offset, position },用於行內完成。
onDefinitionRequest.on_definition_request(|event: &EditorDefinitionRequest| ..)編輯器請求定義:{ id, position, offset, range, trigger }(前往定義)。
onCodeActionRequest.on_code_action_request(|event: &EditorCodeActionRequest| ..)使用者請求程式碼動作:{ range, position, bounds? }。

#無障礙功能

螢幕閱讀器以編輯器的 accessibilityLabel 稱呼它;其焦點行為與文字輸入相同,因此平台的文字服務(輸入法、聽寫、軟體鍵盤)都能在其中運作。

#相關內容

  • TextView:Markdown 與 HTML 文件,其程式碼區塊與編輯器共用語法色彩。
  • DiffView:兩段文字之間的差異,以編輯器的語法色彩顯示。
  • Input:單行與多行文字欄位,使用平台原生的編輯功能。
  • keymap:應用程式為編輯器指令設定的按鍵。
  • nsh/base:其他基礎元件。