add workspace grep engine + search.grep/cancel, editor.open --line

The pure-Dart content-search engine behind find-in-files (D-79): walks
the ignore-pruned workspace, fans files across worker isolates
(Isolate.run) for parallelism, matches each line with a literal
indexOf fast-path or a RegExp, and streams match batches with
cooperative cancellation. No ripgrep dependency; the search.grep IPC
contract is engine-agnostic so an rg accelerator can slot in later.

search.grep returns a searchId and streams search.match / search.done
(or search.error) events, mirroring files.watch; search.cancel stops
an in-flight search. The service reuses the files service's resolved
ignore set so both honour the same ignore_files: layering.

editor.open gains an optional 1-based line argument: it converts the
line to a byte offset and sets the initial selection, enabling
click-to-line from search results.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 20:31:18 +02:00
co-authored by Claude Opus 4.8
parent 0c7a6e86d5
commit 399a4d3a3f
10 changed files with 824 additions and 0 deletions
+22
View File
@@ -11,6 +11,7 @@ library;
import 'dart:io' show FileSystemException;
import '../editor/buffer.dart' show Selection;
import '../editor/registry.dart';
import '../ipc/envelope.dart';
import '../ipc/errno_mapping.dart';
@@ -66,6 +67,14 @@ Future<IpcResponse> _open(IpcRequest req, EditorRegistry r) async {
}
try {
final buf = await r.open(path);
// Optional 1-based line: jump the initial selection to that line's
// start (used by find-in-files click-to-line, T-52). Out-of-range
// lines clamp via setSelection. A non-numeric/<1 line is ignored.
final rawLine = req.args['line'];
final line = rawLine is num ? rawLine.toInt() : int.tryParse('$rawLine');
if (line != null && line >= 1) {
r.setSelection(buf.id, Selection.collapsed(_offsetForLine(buf.content, line)));
}
return IpcResponse.ok(id: req.id, data: buf.toJson());
} on FileSystemException catch (e) {
final errno = e.osError?.errorCode;
@@ -95,6 +104,19 @@ Future<IpcResponse> _open(IpcRequest req, EditorRegistry r) async {
}
}
/// Byte offset of the start of the 1-based [line] in [content]. Lines
/// past the end clamp to the content length.
int _offsetForLine(String content, int line) {
if (line <= 1) return 0;
var remaining = line - 1;
var i = 0;
while (i < content.length && remaining > 0) {
if (content.codeUnitAt(i) == 0x0A) remaining--;
i++;
}
return i;
}
Future<IpcResponse> _active(IpcRequest req, EditorRegistry r) async {
final buf = r.active;
if (buf == null) {