Skip to content

feat(desktop): Search タブの全文検索を VSCode 準拠に拡張 (#359 第2弾)#369

Merged
MocA-Love merged 6 commits intomainfrom
fix/search-tab-vscode-parity
Apr 21, 2026
Merged

feat(desktop): Search タブの全文検索を VSCode 準拠に拡張 (#359 第2弾)#369
MocA-Love merged 6 commits intomainfrom
fix/search-tab-vscode-parity

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 20, 2026

Summary

PR #365 (Cmd+P / Files タブの .gitignore 尊重と MRU boost) の続編。右サイドバー Search タブ (ripgrep による全文検索 + 置換) を VSCode Search View と同等に拡張する。

Issue: #359

追加機能

コア

機能 実装
Match whole word --word-regexp + 置換/ハイライトで \b(?:...)\b
Multiline regex Regex モード時のみ UI 表示。--multiline --multiline-dotall
scopeId による cancellation 分離 ${rootPath}::${scopeId} で AbortController キーを分離
外部 AbortSignal 連結 SearchContentOptions.signal を内部 controller に forward

ストリーミング検索結果表示

  • workspace-fs/searchContentStream を新設: spawn ベースで ripgrep --json を逐次 parse、AsyncIterable で match を 1 件ずつ yield
  • RunRipgrepStream 型と spawnRipgrep オプションを導入、batched runRipgrep と並行して注入可能
  • FsSubscriptionMapsearchContentStream を追加
  • apps/desktop tRPC に observable-based subscription を追加 (AGENTS.md の trpc-electron 制約準拠)
  • apps/desktop で @vscode/ripgrep bundled バイナリを spawn する spawnBundledRipgrep を追加
  • useContentSearchuseSubscription ベースに書き換え、match 到着のたびに state に append。巨大リポでも数件目から UI に表示される

置換プレビュー (適用前 inline diff)

  • buildLineReplacementSegments 純粋関数: 1 行に対して {kind, text}[] (元テキスト / 削除対象 / 追加テキスト) のセグメント列を返す。$1 等のキャプチャグループは backend と同じ semantics
  • SearchMatchItemreplacement prop 追加、<del> (赤取消し) + <ins> (緑) の diff 表示
  • SearchFileGroup / SearchTreeNode 経由で propagate

挙動改善

  • searchContent のデフォルト includeHiddentruefalse に変更 (searchFiles と揃える)
  • ripgrep の ./ プレフィックスを normalizePathForGlob で除去し、fast-glob 経路と relativePath を一致
  • replaceContent にも wholeWord / multiline を追加
  • CI Test ジョブに @vscode/ripgrep の postinstall を明示実行するステップを追加 (--ignore-scripts 下でも bundled rg が展開される)

テスト (packages/workspace-fs/src/search.test.ts)

新規 10 件 (24 tests total, all pass):

  • searchContent: .gitignore 尊重 / includeHidden=true で reveal / whole word / multiline / scopeId 分離
  • replaceContent: キャプチャグループ ($1) / whole word
  • searchContentStream: 逐次 yield / limit 上限 / AbortSignal による途中停止

テストヘルパー強化

  • createTempRootfs.realpath を通す (macOS の /var → /private/var symlink で writeFile の root 境界チェックが誤検出するのを回避)
  • @vscode/ripgrepdevDependency に追加、bundledRunRipgrep / bundledSpawnRipgrep を提供
  • it.skipIf(!BUNDLED_RG_AVAILABLE) で ripgrep バイナリ未展開の環境では rg 依存テストを skip

レビュー対応 (Codex)

  • P1: クエリ編集時にストリーム結果がリセットされないsubscriptionKey (primitive identity) を useEffect deps に入れて確実にリセット
  • P2: 長時間ストリームで完了タイマーが誤発火idleTimerRef + resetIdleTimer に書き換え、各 onData でタイマー再スタート
  • P2: multi-line regex の per-match replace が空振りcanInlineReplace = !(isRegex && multiline) で該当モードでは単行 Replace ボタンを無効化 (Replace All は健在)

非スコープ

  • 検索履歴 / 保存検索
  • files.exclude / search.exclude 設定連携 (そもそも Superset に settings 機構が薄い)

Test plan

  • Whole Word トグル ON で部分一致が除外されること
  • Regex モード ON で Multiline トグルが表示され、function\s+foo[\s\S]*return のようなパターンがマッチすること
  • Search タブ検索中に Cmd+P を開いて別検索しても双方の結果が潰れないこと
  • .gitignore されたファイルが結果に出ないこと
  • $1 を使った正規表現置換が動くこと
  • 巨大リポで検索を実行し、最初の数件が即時に表示される (ストリーミング)
  • 置換入力欄に値を入れると各行に before→after diff プレビューが出る
  • bun run typecheck / bun run lint / bun test 全通過

PR #365 に続く第2段として、右サイドバー Search タブ (ripgrep ベースの
ファイル内容検索) に VSCode で一般的に期待される機能を追加した。

## 追加機能
- **Match whole word (`\b...\b`)**: ripgrep の `--word-regexp` を使い、
  部分一致ではなく単語単位でのみマッチさせる。置換も `\b(?:query)\b`
  を適用するのでインライン置換が `foo` にマッチして `foobar` を
  誤置換することはない
- **Multiline regex**: 正規表現モード時のみ表示される。ripgrep の
  `--multiline --multiline-dotall` を使い、パターンが改行をまたいで
  マッチできるようにする (`.` も改行に一致)
- **scopeId による cancellation 分離**: searchContent も searchFiles と
  同じく `${rootPath}::${scopeId}` で AbortController のキーを分離。
  Search タブ / Cmd+P / Files タブが同時に走っても互いを abort しない
- **外部 AbortSignal のサポート**: `SearchContentOptions.signal` を追加
  し、内部 controller と連結する。cleanup で listener も撤去

## 挙動改善
- `searchContent` のデフォルト `includeHidden` を `true` → `false` に
  変更。searchFiles と揃えつつ、UI は従来どおり明示的に `false` を
  渡しているので既存パスに影響しない
- ripgrep の出力に含まれる `./` プレフィックスを除去して relativePath
  を fast-glob 経路と揃えた (テストで fragile な `"./a.ts"` 比較を
  避けるため)
- `replaceContent` にも `wholeWord` / `multiline` を導入

## UI
- `SearchToolbar` に Whole Word / Multiline のトグルを追加
  (Multiline は Regex モード時のみ表示)
- 置換・ハイライトの全パスに wholeWord / multiline を propagate

## テスト (packages/workspace-fs/src/search.test.ts)
新規 7 件:
- searchContent が .gitignore を尊重するか
- includeHidden=true で ignored ファイルも出るか
- wholeWord で部分一致を除外できるか
- multiline で改行をまたいだ regex がマッチするか
- scopeId 分離で並行呼び出しが互いを cancel しないか
- replaceContent のキャプチャグループ置換 ($1 等)
- replaceContent の wholeWord 置換

テストヘルパーも強化:
- createTempRoot が fs.realpath を通す (macOS の /var → /private/var
  symlink で writeFile の root 境界チェックが誤検出するのを回避)
- @vscode/ripgrep を devDependency に追加し、bundledRunRipgrep という
  テスト用ランナーを提供 (CI に rg が入っていない環境でも ripgrep
  固有機能のテストが走るように)

Refs: Issue #359 (第2弾)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

CI ワークフローに ripgrep バイナリのセットアップステップを追加し、デスクトップ検索機能にストリーミング API を実装。「全単語一致」「複数行」フラグの対応を追加し、UI コンポーネント、検索ユーティリティ、バックエンドサービス層全体で拡張。新しいテストカバレッジも含む。

Changes

Cohort / File(s) Summary
CI セットアップ
.github/workflows/ci.yml
デスクトップネイティブ依存関係をインストール後、@vscode/ripgrep の postinstall スクリプトを明示的に実行するステップを追加。
バックエンド tRPC ルーター
apps/desktop/src/lib/trpc/routers/filesystem/index.ts, packages/host-service/src/trpc/router/filesystem/filesystem.ts
searchContent/replaceContent スキーマに wholeWordmultilinescopeId パラメータを追加。新しい searchContentStream subscription エンドポイントを実装。
検索サービス実装
apps/desktop/src/lib/trpc/routers/workspace-fs-service.ts, packages/workspace-fs/src/search.ts, packages/workspace-fs/src/host/service.ts
バンドル済み ripgrep のストリーミング起動ロジック spawnBundledRipgrep を追加。searchContentStream API を実装し、段階的にマッチ結果をストリーミング。JSON 行パース、重複排除、キャンセル信号サポートを組み込む。
フロントエンド検索 UI コンポーネント
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/SearchView/SearchView.tsx, SearchToolbar.tsx, SearchFileGroup.tsx, SearchMatchItem.tsx, SearchTreeNode.tsx
wholeWordmultiline トグルを追加。replacement prop による差分プレビューレンダリングに対応。props を階層化してコンポーネント間で伝播。
検索フック・ユーティリティ
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/SearchView/hooks/useContentSearch/useContentSearch.ts, utils/searchPattern/searchPattern.tsx
useQuery から useSubscription に移行してストリーミング結果を蓄積。結果の重複排除、アイドルタイムアウト (400ms)、リセット動作を実装。regex フラグの全単語一致・複数行モード、行別置換セグメント構築を追加。
型定義・スキーマ
packages/workspace-fs/src/core/service.ts, packages/workspace-fs/src/client/index.ts, packages/workspace-fs/package.json, bun-test.d.ts
FsContentStreamInput/FsContentStreamEvent インターフェースを追加。クライアントに searchContentStream subscription メソッドを公開。@vscode/ripgrep devDependency を追加。bun:testit API に skip/skipIf メソッドを追加。
テスト
packages/workspace-fs/src/search.test.ts
ripgrep 可用性の条件付きスキップ機構。bundled ripgrep ラッパー、.gitignore 処理、全単語一致、複数行モード、キャンセル動作のテストカバレッジを追加。

Sequence Diagram

sequenceDiagram
    participant User as ユーザー
    participant SearchView as SearchView<br/>(フロントエンド)
    participant useContentSearch as useContentSearch<br/>(フック)
    participant tRPC as tRPC<br/>Subscription
    participant SearchService as SearchService<br/>(バックエンド)
    participant Ripgrep as Ripgrep<br/>(バイナリ)

    User->>SearchView: 検索クエリを入力<br/>(wholeWord/multiline)
    SearchView->>useContentSearch: query & flags を渡す
    useContentSearch->>tRPC: searchContentStream を<br/>subscribe開始
    tRPC->>SearchService: 検索パラメータを転送
    SearchService->>Ripgrep: ripgrep プロセスを spawn<br/>(--json フラグ付き)
    
    loop ストリーミング処理
        Ripgrep->>SearchService: JSON マッチ行を出力
        SearchService->>SearchService: JSON をパース<br/>重複を排除
        SearchService->>tRPC: FsContentMatch を emit
        tRPC->>useContentSearch: マッチイベント受信
        useContentSearch->>useContentSearch: searchResults に蓄積
        useContentSearch->>SearchView: 結果を更新
        SearchView->>User: マッチを段階的に表示
    end
    
    User->>SearchView: (リセット)<br/>キャンセル信号
    SearchView->>useContentSearch: subscription クリア
    useContentSearch->>SearchService: AbortSignal 発火
    SearchService->>Ripgrep: プロセス終了 (SIGTERM)
    Ripgrep->>SearchService: 終了
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 ストリームが流れて、マッチよ踊れ
単語しっかり、複数行もね
ripgrep のバイナリ、走れよ走れ
インクリメンタルに、結果ぱたぱた
検索も置換も、キャンセルも楽々

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR説明は詳細で、追加機能・UI変更・テスト・既知の非スコープが明確に記述されている。ただしテンプレートの必須セクション「Type of Change」「Testing」の形式に従っていない。 テンプレートのセクション「Type of Change」チェックボックスと「Testing」に対応する検証内容を明示的に記入すること。現在の説明は充実しているが、テンプレート形式を整えることで一貫性が向上します。
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは「feat(desktop): Search タブの全文検索を VSCode 準拠に拡張 (#359 第2弾)」で、変更の主要な目的(Search タブを VSCode 準拠に拡張すること)を明確に表現しています。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/search-tab-vscode-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5358b9e93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

VSCode Search View と UX を揃えるための2機能を追加した。どちらも本 PR の
既存機能 (whole word / multiline / scopeId 分離) と地続きの挙動改善。

## 1. ストリーミング検索結果表示
- workspace-fs に `searchContentStream` を追加。`spawn` ベースで ripgrep
  `--json` を逐次 parse し、AsyncIterable で1件ずつ yield する
- `RunRipgrepStream` type と `spawnRipgrep` オプションを導入し、batched
  版 `runRipgrep` と平行して注入できるようにした
- FsService.searchContentStream と FsSubscriptionMap に登録
- apps/desktop の tRPC に `searchContentStream` subscription を追加
  (observable パターン、AGENTS.md の trpc-electron 制約準拠)
- apps/desktop で `@vscode/ripgrep` の bundled バイナリを spawn する
  実装を `spawnBundledRipgrep` として追加
- useContentSearch を `useSubscription` ベースに書き換え、match が届く
  たびに state に append。巨大リポでも最初の数件が即座に表示される

## 2. 置換プレビュー (適用前 inline diff)
- `buildLineReplacementSegments` を追加: 1 行に対しクエリを適用し
  `{kind, text}[]` のセグメント列 (元テキスト / 削除対象 / 追加テキスト)
  を返す純粋関数。regex の `.replace(replacement)` で `$1` 等の置換
  パターンを backend と同じ semantics で解決
- SearchMatchItem に `replacement` prop を追加。replaceOpen + replacement
  非空のとき、既存のハイライト描画を <del>(赤取消) + <ins>(緑) の
  before/after 表示に切り替える
- SearchFileGroup / SearchTreeNode にも propagate

## テスト (packages/workspace-fs/src/search.test.ts)
新規 3 件 (24 tests total, all pass):
- searchContentStream が match を逐次 yield するか (5 ファイル確認)
- limit 指定で runaway queries を止められるか
- AbortSignal で途中停止しつつ runaway を防げるか

Refs: Issue #359 (第2弾の追加機能)
CI (Linux runner) は \`bun install\` 時に \`@vscode/ripgrep\` の postinstall
を走らせないケースがあり、bin/rg バイナリが node_modules に存在しない。
その結果、bundledRgPath を spawn/execFile した瞬間に ENOENT になり、以下
のテストが失敗していた:
- searchContent > multiline=true lets regex span newlines
- searchContentStream > yields each match incrementally ...
- searchContentStream > honors limit ...
- searchContentStream > cancels streaming ...

これらのテストは「ripgrep が実際に走った場合の挙動」を固定するもので、
fallback (searchContentWithScan) では再現できない。rg が無いなら本番
desktop でも @vscode/ripgrep が同梱されているので CI での実動は担保
できないが、ロジックの回帰検出には他のテストで十分。

bun:test の \`it.skipIf\` を使い、bundledRgPath が存在しない環境では
当該テストを skip する。type 定義 (bun-test.d.ts) にも追加。
\`bun install --frozen --ignore-scripts\` は安全のため全パッケージの
postinstall を止めているので、\`@vscode/ripgrep\` が platform-specific な
ripgrep バイナリをダウンロードする処理も走らない。結果として Test ジョブで
bundledRgPath が ENOENT になり、streaming / multiline のテストが落ちて
いた。

当該パッケージの postinstall だけを後から明示的に走らせるステップを
追加し、CI でも実際に ripgrep を呼ぶテストが通るようにする。
@MocA-Love
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8124e3a4f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

## P1: クエリ編集時にストリーム結果がリセットされない
useContentSearch のリセット効果が deps=[] で一度しか走らず、検索テキストや
オプションを編集しても古い match が残り、新しい stream event が append
されてしまっていた。結果としてユーザーが "Replace match" を押すと古い
結果に対して置換を適用してしまう。クエリ identity を primitive string
(subscriptionKey) に落とし込み、useEffect のキーとして使うことで確実に
リセットされるよう修正。

## P2: 長時間ストリーム中に完了ヒューリスティックが誤発火
完了判定の 400ms タイマーが isStreaming=true になった時点で開始される
だけで、以降の onData では更新されなかった。400ms を超えて emit が続く
検索では途中で isStreaming が false に倒れ、UI が「完了」表示になる。
idleTimerRef + resetIdleTimer useCallback に書き換え、each event ごと
にタイマーを再スタート。unmount 時の cleanup も追加。

## P2: multi-line regex モードで単行 replace が空振り
replaceSearchMatchesInLineInContent は1物理行しか扱えないため、
foo\nbar 系の multi-line 検索では match は出るが置換できずに
"out-of-date" エラーになる。canInlineReplace を
\`!(isRegex && multiline)\` で落とし、該当モードでは per-match の
Replace ボタンを無効化 (Replace All はバックエンドが処理するので健在)。

Refs: PR #369 (Codex review comments)
@MocA-Love MocA-Love merged commit ac11f4b into main Apr 21, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant