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

元素

編輯器

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 grammar,或應用程式自己的解析器)為任何語言突出顯示。register_language 註冊一個 EditorLanguage:包括它的名稱、別名、副檔名和檔案名稱,以及編輯規則(註解、括號、輸入時自動閉合的符號對、單字字元、縮排),並以 syntax 指定用來突出顯示它的內建 grammar。register_highlighter 為任何語言(內建或已註冊)指定應用程式的突出顯示器:即實作 EditorHighlighter 的型別,它會接收每次編輯(行、位元組和位置,格式與 tree-sitter 的編輯相同),並在時間預算內於 UI 執行緒以外的地方提供每一行的 token(EditorToken,或透過 EditorToken::from_scope 提供 tree-sitter capture 或 TextMate scope)。編輯器會保存這些 token,在文字被編輯時讓它們隨文字移動,並像對待內建突出顯示器一樣,再次為可見的行索取 token。language 會先找出已註冊的語言(依名稱、別名、副檔名或路徑),在編輯器和 diff 檢視中皆然。

每個引擎各自保存自己的語言。Rust 應用程式須在掛載編輯器之前,於其根部註冊這些語言;對於 TSX 應用程式,則由應用程式的 crate 在其 UI set 的設定階段註冊,而每個運行該應用程式的引擎都會先呼叫這個設定。

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)。游標所在的括號及其配對括號會以外框標示。
  • 復原 會撤回整段輸入:一個單字、一次刪除、一個指令。移動或按下滑鼠會開始新的一步。
  • 摺疊。 後面接著縮排更深的行的一行,可把那些行摺疊起來,並在邊欄顯示標記:⌄ 摺疊,› 和該行之後的 ⋯ 展開。游標移入摺疊範圍時會將其展開(摺疊)。
  • 按下操作。 使用指標裝置時,按一下會放置游標,按兩下選取單字,按三下選取整行,Shift-click 擴展選取範圍,拖曳則依按下時選取的單位進行選取。Alt+Shift 拖曳(Option+Shift;Linux 上亦可用 Control+Alt)會選取區塊:每行一個選取範圍,位於相同的欄之間,並會延伸越過較短行的行尾。在觸控螢幕上,輕按一下會放置游標,輕按兩下或長按會選取手指下的單字,而選取範圍兩端的控點可隨手指拖曳移動各端(控點周圍的文字仍會捲動)。
  • 剪貼簿。 沒有選取範圍時複製和剪下,會取得游標所在的整行,貼上時這些內容會以整行形式貼到游標所在行的上方。

readOnly 會保持文字原樣,同時仍可選取、搜尋和複製。

#按鍵

編輯器的按鍵只在其取得焦點時有效;其他輸入元件保留各自的按鍵。mod 在 macOS 和 iOS 上是 Command,在其他平台是 Control(keymap 的按鍵操作)。

按鍵(macOS) 按鍵(Windows、Linux) 指令
方向鍵,配合 Shift 選取 方向鍵,配合 Shift 選取 按字元和行移動
alt+left / alt+right ctrl+left / ctrl+right 按單字移動
cmd+left / cmd+right、home / end、ctrl+a / ctrl+e home / end 行的第一個非空白字元(再按則到行首)/ 行尾
cmd+up / cmd+down ctrl+home / ctrl+end 文字的開頭 / 結尾
pageup / pagedown pageup / pagedown 向上 / 向下一頁
alt+backspace / alt+delete ctrl+backspace / ctrl+delete 向左 / 向右刪除一個單字
cmd+backspace / cmd+delete、ctrl+k ctrl+shift+backspace / ctrl+shift+delete 刪除至行首 / 行尾
cmd+z / cmd+shift+z ctrl+z / ctrl+shift+z、ctrl+y 復原 / 重做
cmd+a ctrl+a 全選
cmd+l ctrl+l 選取整行(再按:連同下一行)
cmd+d ctrl+d 選取下一個出現位置
cmd+shift+l ctrl+shift+l 選取所有出現位置
cmd+alt+up / cmd+alt+down ctrl+alt+up / ctrl+alt+down 在上方 / 下方新增游標
escape escape 回到單一選取範圍
alt+up / alt+down alt+up / alt+down 向上 / 向下移動行
alt+shift+up / alt+shift+down alt+shift+up / alt+shift+down 向上 / 向下複製行
cmd+shift+k ctrl+shift+k 刪除行
cmd+enter / cmd+shift+enter ctrl+enter / ctrl+shift+enter 在下方 / 上方新增一行
tab / shift+tab、cmd+] / cmd+[ tab / shift+tab、ctrl+] / ctrl+[ 縮排 / 取消縮排
cmd+/ ctrl+/ 切換行註解
cmd+shift+\ ctrl+shift+\ 跳到配對的括號
ctrl+j ctrl+j 合併行
ctrl+t 對調游標前後的字元
ctrl+alt+left / ctrl+alt+right,配合 Shift 選取 按單字的部分移動(camelCase、snake_case)
shift+alt+a shift+alt+a 切換區塊註解
shift+alt+i shift+alt+i 把選取範圍拆分為多行
ctrl+shift+cmd+right / ctrl+shift+cmd+left shift+alt+right / shift+alt+left 擴展 / 縮小選取範圍
cmd+u ctrl+u 回到上一個選取範圍
cmd+g / cmd+shift+g、f3 / shift+f3 f3 / shift+f3 尋找下一個 / 上一個符合項
cmd+e 尋找選取內容(onFindWithSelection)
cmd+alt+[ / cmd+alt+] ctrl+shift+[ / ctrl+shift+] 摺疊 / 展開游標所在行
f12 f12 跳到游標處的定義(onDefinitionRequest)
cmd+. ctrl+. 選取範圍處的 code actions(onCodeActionRequest)
cmd+c / cmd+x / cmd+v ctrl+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)。位置以行和 UTF-16 欄表示(從 0 開始),與語言伺服器相同,因此其診斷可原樣傳入。在應用程式傳入新的診斷之前,標記會在使用者輸入時留在原本的文字上;顯示訊息(清單、行上的懸停卡片)則由應用程式負責。

診斷也帶有語言伺服器 Diagnostic 的其餘欄位。其 tags 會像語言用戶端一般繪製文字:unnecessary 代碼(未使用的變數、無法到達的代碼)會淡化,deprecated 代碼則加上刪除線,涵蓋 unnecessaryText 和 deprecatedText 部分;帶有標籤的 hint 只會顯示其文字。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 會在行號前的欄帶中,於其範圍涵蓋的每一行旁邊加上標記:例如版本控制 diff 新增或修改的行,或斷點(空範圍涵蓋它所在的行,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),取代內建突顯器在該處找到的結果:應用程式自己的突顯器(語言伺服器的 semantic tokens,或自行編寫的解析器)把其 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(滑鼠停在該處時按下,或按住 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 為每個空格顯示 ·,為每個定位字元顯示 →(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 class,styles 則按部分接受行內樣式;某部分的 class 排在其自身預設樣式之後,因此優先於預設樣式,而其 styles 又優先於其 class。字型以 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 輸入法正在組字的文字 加底線
searchMatch search 的符合項目 半透明琥珀色
currentSearchMatch 選取範圍所在的符合項目,套用於 searchMatch 之後 較深的琥珀色
matchingBracket 游標處的括號及其配對括號 1pt 邊框
indentGuide 每個縮排層級一條參考線 border 的 1pt 線
foldPlaceholder 摺疊行在其文字之後顯示的內容 ⋯、muted-foreground
placeholder 文字為空時顯示 muted-foreground
diagnosticError 錯誤下方的線 destructive
diagnosticWarning 警告下方的線 琥珀色
diagnosticInfo 資訊下方的線 藍色
diagnosticHint 提示下方的線 muted-foreground
unnecessaryText unnecessary 診斷的文字 muted-foreground
deprecatedText deprecated 診斷的文字 加刪除線
whitespace 空格和 Tab 的標記(showWhitespaces) · 和 →,muted-foreground 於 60 %
ruler rulers 的線 border 的 1 point 線
selectionHandle 觸控選取範圍兩端把手的圓鈕 primary 的 10pt 圓點
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 定義連結的底線 文字下方 primary 的 1pt 線

#屬性

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