Skip to content

Commit 94a9305

Browse files
lambdalisueclaude
andcommitted
feat: add grep source for vim's :grep command results
Implements a new source that executes vim's :grep command and generates items from the results. Supports pattern search, file specification, and both quickfix and direct output modes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 8a08ae2 commit 94a9305

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

builtin/source/grep.ts

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import * as fn from "@denops/std/function";
2+
3+
import { defineSource, type Source } from "../../source.ts";
4+
5+
type Detail = {
6+
/**
7+
* File path
8+
*/
9+
path: string;
10+
11+
/**
12+
* Line number
13+
*/
14+
line: number;
15+
16+
/**
17+
* Column number
18+
*/
19+
column: number;
20+
21+
/**
22+
* Matched text
23+
*/
24+
text: string;
25+
26+
/**
27+
* The pattern that was searched
28+
*/
29+
pattern: string;
30+
};
31+
32+
export type GrepOptions = {
33+
/**
34+
* The pattern to search for.
35+
* If not provided, uses the last search pattern.
36+
*/
37+
pattern?: string;
38+
39+
/**
40+
* Files to search in.
41+
* Defaults to current file if not specified.
42+
*/
43+
files?: string[];
44+
45+
/**
46+
* Additional grep flags.
47+
*/
48+
flags?: string;
49+
50+
/**
51+
* Whether to use the quickfix list.
52+
* If false, returns results directly without populating quickfix.
53+
* @default true
54+
*/
55+
useQuickfix?: boolean;
56+
};
57+
58+
/**
59+
* Creates a Source that generates items from Vim's :grep command results.
60+
*
61+
* This Source executes Vim's :grep command and generates items from the results.
62+
* It uses the 'grepprg' and 'grepformat' settings to parse the output.
63+
*
64+
* @param options - Options to customize grep execution.
65+
* @returns A Source that generates items representing grep results.
66+
*/
67+
export function grep(
68+
options: Readonly<GrepOptions> = {},
69+
): Source<Detail> {
70+
const pattern = options.pattern;
71+
const files = options.files ?? ["%"];
72+
const flags = options.flags ?? "";
73+
const useQuickfix = options.useQuickfix ?? true;
74+
75+
return defineSource(async function* (denops, _params, { signal }) {
76+
// Get the pattern to search for
77+
let searchPattern = pattern;
78+
if (!searchPattern) {
79+
// Use last search pattern
80+
searchPattern = await fn.getreg(denops, "/") as string;
81+
if (!searchPattern) {
82+
return;
83+
}
84+
}
85+
86+
signal?.throwIfAborted();
87+
88+
// Build grep command
89+
const args = [searchPattern, ...files];
90+
const grepCmd = flags
91+
? `:grep! ${flags} ${
92+
args.map((a) => `'${a.replace(/'/g, "''")}'`).join(" ")
93+
}`
94+
: `:grep! ${args.map((a) => `'${a.replace(/'/g, "''")}'`).join(" ")}`;
95+
96+
try {
97+
if (useQuickfix) {
98+
// Execute grep command
99+
await denops.cmd(`silent! ${grepCmd}`);
100+
signal?.throwIfAborted();
101+
102+
// Get results from quickfix list
103+
const qflist = await fn.getqflist(denops) as Array<{
104+
bufnr: number;
105+
lnum: number;
106+
col: number;
107+
text: string;
108+
valid: number;
109+
}>;
110+
111+
// Clear quickfix list to avoid side effects
112+
await denops.cmd("cexpr []");
113+
114+
let id = 0;
115+
for (const item of qflist) {
116+
if (!item.valid) {
117+
continue;
118+
}
119+
120+
// Get filename from buffer number
121+
let filename = "";
122+
if (item.bufnr > 0) {
123+
filename = await fn.bufname(denops, item.bufnr) as string;
124+
}
125+
126+
if (!filename) {
127+
continue;
128+
}
129+
130+
// Format display value
131+
const locationStr = `${filename}:${item.lnum}:${item.col}`;
132+
const value = `${locationStr}: ${item.text}`;
133+
134+
yield {
135+
id: id++,
136+
value,
137+
detail: {
138+
path: filename,
139+
line: item.lnum,
140+
column: item.col,
141+
text: item.text,
142+
pattern: searchPattern,
143+
},
144+
};
145+
}
146+
} else {
147+
// Execute grep directly and parse output
148+
const grepprg = await denops.eval("&grepprg") as string;
149+
const grepformat = await denops.eval("&grepformat") as string;
150+
151+
// This is a simplified implementation
152+
// In practice, we'd need to properly parse grepformat
153+
// For now, assume standard grep output format
154+
const output = await denops.call(
155+
"system",
156+
`${grepprg} ${args.join(" ")}`,
157+
) as string;
158+
159+
if (output) {
160+
const lines = output.trim().split("\n");
161+
let id = 0;
162+
163+
for (const line of lines) {
164+
// Simple parsing for "filename:line:column:text" format
165+
const match = line.match(/^([^:]+):(\d+):(\d+)?:?(.*)$/);
166+
if (match) {
167+
const [, filename, lineStr, colStr, text] = match;
168+
const lineNum = parseInt(lineStr, 10);
169+
const colNum = colStr ? parseInt(colStr, 10) : 1;
170+
171+
yield {
172+
id: id++,
173+
value: line,
174+
detail: {
175+
path: filename,
176+
line: lineNum,
177+
column: colNum,
178+
text: text.trim(),
179+
pattern: searchPattern,
180+
},
181+
};
182+
}
183+
}
184+
}
185+
}
186+
} catch (error) {
187+
// Grep might return non-zero exit code if no matches found
188+
// This is not an error condition
189+
if (error instanceof Error && !error.message.includes("E480")) {
190+
throw error;
191+
}
192+
}
193+
});
194+
}

builtin/source/mod.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// This file is generated by gen-mod.ts
2+
export * from "./autocmd.ts";
23
export * from "./buffer.ts";
34
export * from "./colorscheme.ts";
45
export * from "./command.ts";
56
export * from "./file.ts";
67
export * from "./git_status.ts";
8+
export * from "./grep.ts";
79
export * from "./helptag.ts";
810
export * from "./highlight.ts";
911
export * from "./history.ts";
@@ -18,4 +20,5 @@ export * from "./oldfiles.ts";
1820
export * from "./quickfix.ts";
1921
export * from "./register.ts";
2022
export * from "./tabpage.ts";
23+
export * from "./vimgrep.ts";
2124
export * from "./window.ts";

deno.jsonc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,54 @@
3737
"./builtin/previewer/file": "./builtin/previewer/file.ts",
3838
"./builtin/previewer/helptag": "./builtin/previewer/helptag.ts",
3939
"./builtin/previewer/noop": "./builtin/previewer/noop.ts",
40+
"./builtin/previewer/shell": "./builtin/previewer/shell.ts",
4041
"./builtin/refiner": "./builtin/refiner/mod.ts",
4142
"./builtin/refiner/absolute-path": "./builtin/refiner/absolute_path.ts",
43+
"./builtin/refiner/buffer-info": "./builtin/refiner/buffer_info.ts",
4244
"./builtin/refiner/cwd": "./builtin/refiner/cwd.ts",
4345
"./builtin/refiner/exists": "./builtin/refiner/exists.ts",
46+
"./builtin/refiner/file-info": "./builtin/refiner/file_info.ts",
4447
"./builtin/refiner/noop": "./builtin/refiner/noop.ts",
4548
"./builtin/refiner/regexp": "./builtin/refiner/regexp.ts",
4649
"./builtin/refiner/relative-path": "./builtin/refiner/relative_path.ts",
4750
"./builtin/renderer": "./builtin/renderer/mod.ts",
4851
"./builtin/renderer/absolute-path": "./builtin/renderer/absolute_path.ts",
52+
"./builtin/renderer/buffer-info": "./builtin/renderer/buffer_info.ts",
53+
"./builtin/renderer/file-info": "./builtin/renderer/file_info.ts",
4954
"./builtin/renderer/helptag": "./builtin/renderer/helptag.ts",
5055
"./builtin/renderer/nerdfont": "./builtin/renderer/nerdfont.ts",
5156
"./builtin/renderer/noop": "./builtin/renderer/noop.ts",
5257
"./builtin/renderer/relative-path": "./builtin/renderer/relative_path.ts",
58+
"./builtin/renderer/smart-grep": "./builtin/renderer/smart_grep.ts",
5359
"./builtin/renderer/smart-path": "./builtin/renderer/smart_path.ts",
5460
"./builtin/sorter": "./builtin/sorter/mod.ts",
5561
"./builtin/sorter/lexical": "./builtin/sorter/lexical.ts",
5662
"./builtin/sorter/noop": "./builtin/sorter/noop.ts",
5763
"./builtin/sorter/numerical": "./builtin/sorter/numerical.ts",
5864
"./builtin/source": "./builtin/source/mod.ts",
65+
"./builtin/source/autocmd": "./builtin/source/autocmd.ts",
5966
"./builtin/source/buffer": "./builtin/source/buffer.ts",
67+
"./builtin/source/colorscheme": "./builtin/source/colorscheme.ts",
68+
"./builtin/source/command": "./builtin/source/command.ts",
6069
"./builtin/source/file": "./builtin/source/file.ts",
70+
"./builtin/source/git-status": "./builtin/source/git_status.ts",
71+
"./builtin/source/grep": "./builtin/source/grep.ts",
6172
"./builtin/source/helptag": "./builtin/source/helptag.ts",
73+
"./builtin/source/highlight": "./builtin/source/highlight.ts",
6274
"./builtin/source/history": "./builtin/source/history.ts",
75+
"./builtin/source/jumplist": "./builtin/source/jumplist.ts",
6376
"./builtin/source/line": "./builtin/source/line.ts",
6477
"./builtin/source/list": "./builtin/source/list.ts",
78+
"./builtin/source/loclist": "./builtin/source/loclist.ts",
79+
"./builtin/source/mapping": "./builtin/source/mapping.ts",
80+
"./builtin/source/mark": "./builtin/source/mark.ts",
6581
"./builtin/source/noop": "./builtin/source/noop.ts",
6682
"./builtin/source/oldfiles": "./builtin/source/oldfiles.ts",
6783
"./builtin/source/quickfix": "./builtin/source/quickfix.ts",
84+
"./builtin/source/register": "./builtin/source/register.ts",
85+
"./builtin/source/tabpage": "./builtin/source/tabpage.ts",
86+
"./builtin/source/vimgrep": "./builtin/source/vimgrep.ts",
87+
"./builtin/source/window": "./builtin/source/window.ts",
6888
"./builtin/theme": "./builtin/theme/mod.ts",
6989
"./builtin/theme/ascii": "./builtin/theme/ascii.ts",
7090
"./builtin/theme/double": "./builtin/theme/double.ts",

0 commit comments

Comments
 (0)