Skip to content

Commit 7a0eed7

Browse files
author
SqlRush
committed
Add Grep no-text support
1 parent f0173f1 commit 7a0eed7

4 files changed

Lines changed: 38 additions & 5 deletions

File tree

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ test/parity/ # golden tests against TS/official behavior
212212
- 本轮补充:Grep 路径过滤现在支持 ripgrep 风格 `iglob`/`--iglob` 大小写不敏感 glob,和 `glob` 共享正向/`!` 排除规则集合并语义,structured content 会回传归一化后的 `iglob` 过滤值。
213213
- 本轮补充:Grep 路径过滤现在支持 ripgrep 风格 `glob_case_insensitive`/`globCaseInsensitive`/`glob-case-insensitive`/`--glob-case-insensitive` 及 no-override aliases,让普通 `glob`/`--glob`/`-g` 按大小写不敏感方式匹配。
214214
- 本轮补充:Grep `glob`/`--glob`/`-g` 路径过滤现在支持 ripgrep 风格 `!pattern` 排除规则,可与正向 glob、逗号/空白多 pattern 和 brace alternation 组合;只有排除规则时默认包含未被排除的路径。
215-
- 本轮补充:Grep 搜索现在支持 ripgrep 风格 `text`/`--text`/`-a`,可显式把二进制扩展名文件按文本读取参与匹配,structured content 会回传 `text` 状态`-a` 同样兼容 quoted semantic boolean。
215+
- 本轮补充:Grep 搜索现在支持 ripgrep 风格 `text`/`--text`/`-a``no_text`/`--no-text`,可显式把二进制扩展名文件按文本读取参与匹配,也可用 no-text 覆盖 text 模式`-a` `--no-text` 都兼容 quoted semantic boolean。
216216
- 本轮补充:Grep content 输出现在支持 ripgrep 风格 `passthru`/`passthrough`/`--passthru`/`--passthrough`,可输出被搜索文件的全部行并保留匹配行标记;启用后按官方语义覆盖 context 行数,quoted semantic boolean 同样兼容。
217217
- 本轮补充:Grep content 输出现在支持 ripgrep 风格 `trim`/`--trim``no_trim`/`--no-trim`,会删除每条已打印文本行开头的 ASCII 空白,同时保留原始匹配列号;quoted semantic boolean 同样兼容。
218218
- 本轮补充:Grep 长行输出现在支持 ripgrep 风格 `max_columns_preview`/`--max-columns-preview``no_max_columns_preview`/`--no-max-columns-preview`,会在 `max_columns` 触发时输出截断预览加官方 omitted-end 后缀;quoted semantic boolean 同样兼容。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Covered behavior:
140140
- `Grep` now accepts ripgrep-style case-insensitive glob filtering through `iglob`/`--iglob`, sharing positive and `!` negative glob rule semantics with `glob` while preserving structured filter metadata.
141141
- `Grep` now accepts ripgrep-style `glob_case_insensitive`/`--glob-case-insensitive` plus no-override aliases, making ordinary `glob`/`--glob`/`-g` filters match paths case-insensitively while preserving `iglob` behavior.
142142
- `Grep` glob filtering now accepts ripgrep-style negated `!pattern` rules, including combinations with positive patterns, comma/whitespace splitting, and brace-expanded globs.
143-
- `Grep` now accepts `text`/`--text`/`-a` to search binary-extension files as text while preserving the default binary-extension skip behavior.
143+
- `Grep` now accepts `text`/`--text`/`-a` to search binary-extension files as text while preserving the default binary-extension skip behavior; `no_text`/`--no-text` disables text mode when both are present.
144144
- `Grep` now accepts `line_regexp`/`line-regexp`/`--line-regexp`/`-x` to require whole-line matches, with fixed-string and word-regexp precedence covered.
145145
- `Grep` content output now accepts `passthru`/`passthrough`/`--passthru`/`--passthrough`, printing all searched lines while preserving matched-line markers and overriding context counts.
146146
- `Grep` content output now accepts `vimgrep`/`--vimgrep`, repeating matching lines once per match with column metadata while preserving context-line output and `-N` formatting.

internal/tools/file/search_tools.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ var allowedGrepInputKeys = map[string]struct{}{
5353
"case_sensitive": {}, "caseSensitive": {}, "case-sensitive": {}, "--case-sensitive": {}, "-s": {},
5454
"smart_case": {}, "smartCase": {}, "smart-case": {}, "--smart-case": {}, "-S": {},
5555
"fixed_strings": {}, "fixedStrings": {}, "fixed-strings": {}, "--fixed-strings": {}, "-F": {}, "multiline": {}, "--multiline": {}, "multiline-dotall": {}, "--multiline-dotall": {}, "-U": {},
56-
"text": {}, "--text": {}, "-a": {},
56+
"text": {}, "--text": {}, "-a": {}, "no_text": {}, "noText": {}, "no-text": {}, "--no-text": {},
5757
"word_regexp": {}, "wordRegexp": {}, "word-regexp": {}, "--word-regexp": {}, "-w": {},
5858
"line_regexp": {}, "lineRegexp": {}, "line-regexp": {}, "--line-regexp": {}, "-x": {},
5959
"invert_match": {}, "invertMatch": {}, "invert-match": {}, "--invert-match": {}, "-v": {},
@@ -99,7 +99,7 @@ var grepSemanticBooleanKeys = map[string]struct{}{
9999
"smart_case": {}, "smartCase": {}, "smart-case": {}, "--smart-case": {}, "-S": {},
100100
"fixed_strings": {}, "fixedStrings": {}, "fixed-strings": {}, "--fixed-strings": {}, "-F": {}, "multiline": {}, "--multiline": {}, "multiline-dotall": {}, "--multiline-dotall": {}, "-U": {},
101101
"glob_case_insensitive": {}, "globCaseInsensitive": {}, "glob-case-insensitive": {}, "--glob-case-insensitive": {}, "no_glob_case_insensitive": {}, "noGlobCaseInsensitive": {}, "no-glob-case-insensitive": {}, "--no-glob-case-insensitive": {},
102-
"text": {}, "--text": {}, "-a": {},
102+
"text": {}, "--text": {}, "-a": {}, "no_text": {}, "noText": {}, "no-text": {}, "--no-text": {},
103103
"word_regexp": {}, "wordRegexp": {}, "word-regexp": {}, "--word-regexp": {}, "-w": {},
104104
"line_regexp": {}, "lineRegexp": {}, "line-regexp": {}, "--line-regexp": {}, "-x": {},
105105
"invert_match": {}, "invertMatch": {}, "invert-match": {}, "--invert-match": {}, "-v": {},
@@ -290,6 +290,10 @@ type grepInput struct {
290290
Text bool `json:"text,omitempty"`
291291
LongText bool `json:"--text,omitempty"`
292292
ShortText bool `json:"-a,omitempty"`
293+
NoText bool `json:"no_text,omitempty"`
294+
NoTextAlt bool `json:"noText,omitempty"`
295+
NoTextDash bool `json:"no-text,omitempty"`
296+
LongNoText bool `json:"--no-text,omitempty"`
293297
WordRegexp bool `json:"word_regexp,omitempty"`
294298
WordRegexpAlt bool `json:"wordRegexp,omitempty"`
295299
WordRegexpDash bool `json:"word-regexp,omitempty"`
@@ -682,6 +686,10 @@ func NewGrepTool() tool.Tool {
682686
"text": map[string]any{"type": "boolean"},
683687
"--text": map[string]any{"type": "boolean"},
684688
"-a": map[string]any{"type": "boolean"},
689+
"no_text": map[string]any{"type": "boolean"},
690+
"noText": map[string]any{"type": "boolean"},
691+
"no-text": map[string]any{"type": "boolean"},
692+
"--no-text": map[string]any{"type": "boolean"},
685693
"word_regexp": map[string]any{"type": "boolean"},
686694
"wordRegexp": map[string]any{"type": "boolean"},
687695
"word-regexp": map[string]any{"type": "boolean"},
@@ -826,7 +834,7 @@ func NewGrepTool() tool.Tool {
826834
},
827835
},
828836
PromptFunc: func(tool.PromptContext) (string, error) {
829-
return "Searches text files under path using a regular expression or fixed string. pattern is the canonical search expression; regex/regexp/--regexp/-e are accepted aliases. output_mode may be files, files_with_matches, files_without_matches, content, or count; glob/-g/--glob, iglob/--iglob, type/-t/--type, and type_not/-T/--type-not optionally filter file paths. glob and iglob accept whitespace/comma-separated patterns, negation, and brace alternation; glob_case_insensitive/--glob-case-insensitive makes glob patterns ignore case. content mode supports context, before_context, after_context, -C, -B, -A, -n/--line-number and -N/--no-line-number line-number control, --column column-number output, byte_offset/--byte-offset/-b byte offset output, -H/--with-filename and -I/--no-filename filename prefix control, heading/--heading grouped file headings, path_separator/--path-separator display path separator control, null/--null NUL path terminators/separators, field_match_separator/--field-match-separator and field_context_separator/--field-context-separator output field separators, context_separator/--context-separator and no_context_separator/--no-context-separator context group separator control, offset, head_limit pagination, max_count/-m per-file match limiting, max_columns/--max-columns long-line omission, --max-columns-preview long-line previews, replace/--replace/-r display-only replacement, only_matching/-o/--only-matching matched-text output, vimgrep/--vimgrep per-match line output, passthru/--passthru/--passthrough all-line output, trim/--trim leading-whitespace trimming, and hidden/--hidden or no_hidden/--no-hidden hidden file traversal control. Use files/--files to list files that would be searched without requiring pattern, files_with_matches or -l to list files with matches, files_without_match to list files without matches, and count/--count/-c for count mode. Count mode supports count_matches/--count-matches for occurrence counts and include_zero/--include-zero to include zero-count files. Use max_depth/--max-depth/-d to limit directory descent, follow/--follow/-L or no_follow/--no-follow to control symlink traversal, and sort/--sort or sortr/--sortr with path or modified to control result ordering; --sort-files is accepted as a path-sort alias. Use fixed_strings/-F/--fixed-strings for literal matching, text/-a/--text to search binary-extension files as text, word_regexp/-w/--word-regexp for whole-word matches, line_regexp/-x/--line-regexp for whole-line matches, ignore_case/-i/--ignore-case for case-insensitive search, case_sensitive/-s/--case-sensitive to force case-sensitive matching, smart_case/-S/--smart-case for lowercase-only patterns, and invert_match/-v/--invert-match to select non-matching lines. Set no_ignore/--no-ignore to skip .gitignore/.ignore/.rgignore files, no_ignore_dot/--no-ignore-dot to skip .ignore/.rgignore while keeping .gitignore active, no_ignore_vcs/--no-ignore-vcs to skip .gitignore while keeping .ignore/.rgignore active, ignore_file/--ignore-file to add a gitignore-formatted file matched relative to the current working directory, or no_ignore_files/--no-ignore-files to ignore explicit ignore_file inputs; VCS metadata and read-denied paths remain excluded. Set multiline to allow patterns to span lines with dot matching newlines.", nil
837+
return "Searches text files under path using a regular expression or fixed string. pattern is the canonical search expression; regex/regexp/--regexp/-e are accepted aliases. output_mode may be files, files_with_matches, files_without_matches, content, or count; glob/-g/--glob, iglob/--iglob, type/-t/--type, and type_not/-T/--type-not optionally filter file paths. glob and iglob accept whitespace/comma-separated patterns, negation, and brace alternation; glob_case_insensitive/--glob-case-insensitive makes glob patterns ignore case. content mode supports context, before_context, after_context, -C, -B, -A, -n/--line-number and -N/--no-line-number line-number control, --column column-number output, byte_offset/--byte-offset/-b byte offset output, -H/--with-filename and -I/--no-filename filename prefix control, heading/--heading grouped file headings, path_separator/--path-separator display path separator control, null/--null NUL path terminators/separators, field_match_separator/--field-match-separator and field_context_separator/--field-context-separator output field separators, context_separator/--context-separator and no_context_separator/--no-context-separator context group separator control, offset, head_limit pagination, max_count/-m per-file match limiting, max_columns/--max-columns long-line omission, --max-columns-preview long-line previews, replace/--replace/-r display-only replacement, only_matching/-o/--only-matching matched-text output, vimgrep/--vimgrep per-match line output, passthru/--passthru/--passthrough all-line output, trim/--trim leading-whitespace trimming, and hidden/--hidden or no_hidden/--no-hidden hidden file traversal control. Use files/--files to list files that would be searched without requiring pattern, files_with_matches or -l to list files with matches, files_without_match to list files without matches, and count/--count/-c for count mode. Count mode supports count_matches/--count-matches for occurrence counts and include_zero/--include-zero to include zero-count files. Use max_depth/--max-depth/-d to limit directory descent, follow/--follow/-L or no_follow/--no-follow to control symlink traversal, and sort/--sort or sortr/--sortr with path or modified to control result ordering; --sort-files is accepted as a path-sort alias. Use fixed_strings/-F/--fixed-strings for literal matching, text/-a/--text to search binary-extension files as text, no_text/--no-text to disable text mode, word_regexp/-w/--word-regexp for whole-word matches, line_regexp/-x/--line-regexp for whole-line matches, ignore_case/-i/--ignore-case for case-insensitive search, case_sensitive/-s/--case-sensitive to force case-sensitive matching, smart_case/-S/--smart-case for lowercase-only patterns, and invert_match/-v/--invert-match to select non-matching lines. Set no_ignore/--no-ignore to skip .gitignore/.ignore/.rgignore files, no_ignore_dot/--no-ignore-dot to skip .ignore/.rgignore while keeping .gitignore active, no_ignore_vcs/--no-ignore-vcs to skip .gitignore while keeping .ignore/.rgignore active, ignore_file/--ignore-file to add a gitignore-formatted file matched relative to the current working directory, or no_ignore_files/--no-ignore-files to ignore explicit ignore_file inputs; VCS metadata and read-denied paths remain excluded. Set multiline to allow patterns to span lines with dot matching newlines.", nil
830838
},
831839
NormalizeFunc: normalizeGrepRawInput,
832840
ValidateFunc: validateGrep,
@@ -1126,6 +1134,7 @@ func callGrep(ctx tool.Context, raw json.RawMessage, _ tool.ProgressSink) (contr
11261134
"smart_case": grepSmartCase(input),
11271135
"fixed_strings": grepFixedStrings(input),
11281136
"text": options.Text,
1137+
"no_text": grepNoText(input),
11291138
"word_regexp": grepWordRegexp(input),
11301139
"line_regexp": grepLineRegexp(input),
11311140
"invert_match": invertMatch,
@@ -2289,9 +2298,19 @@ func grepFixedStrings(input grepInput) bool {
22892298
}
22902299

22912300
func grepText(input grepInput) bool {
2301+
if grepNoText(input) {
2302+
return false
2303+
}
22922304
return input.Text || input.LongText || input.ShortText
22932305
}
22942306

2307+
func grepNoText(input grepInput) bool {
2308+
return input.NoText ||
2309+
input.NoTextAlt ||
2310+
input.NoTextDash ||
2311+
input.LongNoText
2312+
}
2313+
22952314
func grepWordRegexp(input grepInput) bool {
22962315
return input.WordRegexp ||
22972316
input.WordRegexpAlt ||

internal/tools/file/tools_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,6 +2897,20 @@ func TestGrepToolTextSearchesBinaryExtensionFiles(t *testing.T) {
28972897
t.Fatalf("text binary result = %#v", textResult)
28982898
}
28992899

2900+
noTextResult, err := executor.Execute(ctx, contracts.ToolUse{
2901+
ID: "toolu_grep_binary_no_text",
2902+
Name: "Grep",
2903+
Input: json.RawMessage(`{"pattern":"Needle","--text":true,"--no-text":"true"}`),
2904+
}, nil)
2905+
if err != nil {
2906+
t.Fatal(err)
2907+
}
2908+
if noTextResult.Content != "No files found" ||
2909+
noTextResult.StructuredContent["text"] != false ||
2910+
noTextResult.StructuredContent["no_text"] != true {
2911+
t.Fatalf("no-text binary result = %#v", noTextResult)
2912+
}
2913+
29002914
shortTextResult, err := executor.Execute(ctx, contracts.ToolUse{
29012915
ID: "toolu_grep_binary_short_text",
29022916
Name: "Grep",

0 commit comments

Comments
 (0)