示例

import { Button, Editor, HStack, Navigation, NavigationStack, Script, Spacer, Text, TextField, useEffect, useMemo, useState, VStack } from "scripting"

// 这个示例基于 EditorController 的交互 API 自建一个搜索 / 替换栏:
//   - searchText  查找所有匹配(编辑器内完成,返回 { start, end, line })
//   - setSelection 高亮当前匹配并滚动到可见
//   - replaceSelection 替换当前选中的匹配
//   - scrollToLine 跳转到指定行
// Scripting 不内置搜索 UI,你可以像这样组合出契合脚本的体验。

function Example() {
  const controller = useMemo(() => new EditorController({
    ext: "ts",
    content: [
      `const greeting = "hello"`,
      `console.log(greeting)`,
      `console.log(greeting.toUpperCase())`,
      `console.log(greeting.length)`,
    ].join("\n"),
  }), [])

  const [query, setQuery] = useState("greeting")
  const [replacement, setReplacement] = useState("message")
  const [matches, setMatches] = useState<EditorTextRange[]>([])
  const [index, setIndex] = useState(0)

  // 不再使用时释放编辑器资源。
  useEffect(() => () => controller.dispose(), [controller])

  async function runSearch() {
    const result = await controller.searchText(query, { caseSensitive: false })
    setMatches(result)
    setIndex(0)
    if (result.length > 0) {
      controller.setSelection(result[0].start, result[0].end)
    }
  }

  function goTo(next: number) {
    if (matches.length === 0) {
      return
    }
    const i = (next + matches.length) % matches.length
    setIndex(i)
    controller.setSelection(matches[i].start, matches[i].end)
  }

  async function replaceCurrent() {
    if (matches.length === 0) {
      return
    }
    // 当前匹配已被选中,直接替换选区;替换后偏移变化,重新搜索。
    controller.replaceSelection(replacement)
    await runSearch()
  }

  return <NavigationStack>
    <VStack
      navigationTitle={"Editor Search"}
      navigationBarTitleDisplayMode={"inline"}
      spacing={8}
      padding
    >
      <HStack>
        <TextField title={"Find"} value={query} onChanged={setQuery} />
        <Button title={"Search"} action={runSearch} />
      </HStack>

      <HStack>
        <TextField title={"Replace"} value={replacement} onChanged={setReplacement} />
        <Button title={"Replace"} action={replaceCurrent} />
      </HStack>

      <HStack>
        <Text>{matches.length > 0 ? `${index + 1} / ${matches.length}` : "No matches"}</Text>
        <Spacer />
        <Button title={"Prev"} action={() => goTo(index - 1)} />
        <Button title={"Next"} action={() => goTo(index + 1)} />
        <Button title={"Top"} action={() => controller.scrollToLine(1)} />
      </HStack>

      <Editor controller={controller} showAccessoryView />
    </VStack>
  </NavigationStack>
}

async function run() {
  await Navigation.present({
    element: <Example />
  })

  Script.exit()
}

run()