Find and folding
Find
The editor has the matcher of a find bar but not the bar: the app draws its own, or uses FindReplace.
import { useEffect, useState } from 'react';
import { Button, Input } from '@adecore/ui';
import { formatNumber } from '@adecore/ui/format';
import type { EditorFindState } from '@adecore/editor';
import { EditorFrame } from '../shared/editor-frame.tsx';
import { SAMPLE, useEditor } from '../shared/editor.ts';
const OPTIONS = { text: SAMPLE, language: 'typescript', theme: 'demo' } as const;
/* A find bar of the host's own, over the editor's matcher. */
export default function FindDemo() {
const { host, editor } = useEditor(OPTIONS);
const [text, setText] = useState('total');
const [state, setState] = useState<EditorFindState>({ count: 0, current: null });
useEffect(() => editor?.onFind(setState), [editor]);
useEffect(() => {
editor?.find(text === '' ? null : { text, caseSensitive: false, wholeWord: false, regex: false });
}, [editor, text]);
return (
<EditorFrame
host={host}
toolbar={
<>
<Input size="sm" aria-label="Find" value={text} onChange={(event) => setText(event.target.value)} className="w-40" />
<span className="text-xs text-text-muted">
{state.current === null ? 'No matches' : `${formatNumber(state.current + 1)} of ${formatNumber(state.count)}`}
</span>
<Button size="xs" onClick={() => editor?.findStep(-1)}>
Previous
</Button>
<Button size="xs" onClick={() => editor?.findStep(1)}>
Next
</Button>
<Button size="xs" onClick={() => editor?.selectFindMatches()}>
Select all
</Button>
</>
}
/>
);
}find(query) marks every match and moves to the first one from the caret; null removes the marks. An EditorFindQuery has text, caseSensitive, wholeWord and regex, and inSelection: true searches only what was selected when it turned on. onFind(listener) reports an EditorFindState: the count, the zero-based current match or null, and noSelection when a search in the selection has nothing selected. It reports again whenever an edit changes the count.
| Method | What it does |
|---|---|
findStep(direction) | Moves to the next (1) or previous (-1) match |
replace(replacement, options?) | Replaces the current match and moves on; false without a match or when read-only |
replaceAll(replacement, options?) | Replaces every match in one undo step and returns how many |
setReplacePreview(replacement) | Draws what a regular expression writes for the current match; null removes it |
selectFindMatches() | Ends the find with a caret on every match and returns how many |
endFind() | Removes the marks and selects the current match |
findFromCursor(query, direction) | Selects the next match from the caret without a find bar |
Matches never touch the selection until endFind. A regular expression expands $1 and the like in the replacement, and preserveCase: true gives it the case of the text it replaces. An invalid regular expression finds nothing, and the editor marks at most 10,000 matches. A slow regular expression has no time limit.
Folding
The editor reads the folding ranges of the model for its language, after a short pause once the text changes. Python and YAML also fold by indentation. A collapsed fold is one row for the line commands: delete, duplicate, comment, move and a copy without a selection take its hidden lines along.
getFolds() returns EditorFolds: the collapsed ranges and the custom ones made from a selection, as zero-based lines. Keep it with the scroll position and the caret, and pass it back as folds when the file opens again.
foldDefaults lists the roles that fold when a file opens without kept folds, such as ['imports', 'file-header']. They fold once: never a range a person opened or closed, never one with the caret in it, and nothing after the first edit. As a language server names more ranges, through hints, those fold too.
setFoldHints(hints) hands over what a language server knows: symbols with their range and body kind, and ranges (EditorFoldRangeHint) with their kind. They name the function, method and class bodies the text cannot tell apart. A range with a startCharacter or an endCharacter, as LSP gives them, folds from within its first line up to within its last: a collapsed import list of use A; to use C; with 3 and 5 reads use …;. The fold the text has of the same lines takes them too. null forgets them.
The fold commands go through runCommand.
Blocks and sticky scroll
The editor reads the blocks of a document, the stretches with a header line such as a function, from brackets and indentation. Sticky scroll pins the headers of the blocks scrolled out of view, up to five lines and at most a quarter of the view. onScope(listener) reports the named blocks around the caret, outermost first, for a breadcrumb.
setBlocks(blocks) replaces those blocks with better ones, such as a language server's symbols: an EditorBlock has one-based startLine and endLine, and an optional name and kind. null goes back to the editor's own.
setSelectionRanges(provider) lets Extend Selection grow through a language server's ranges instead of the model's lexical ones; null goes back.