Skip to content

Commit 3244459

Browse files
Added search files commands
1 parent 54c4a0d commit 3244459

File tree

11 files changed

+328
-142
lines changed

11 files changed

+328
-142
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ Many of the commands take arguments and return values that can only be used with
3131
Format workspace files.
3232
- `andreas.formatSelectedFiles()`
3333
Format selected files. Used by file explorer context menu.
34+
- `andreas.searchFiles(query?: string)`
35+
Search files Search files in workspace.
36+
- `andreas.searchFilesOpenSelected()`
37+
Open selected Open selected files in search result.
38+
- `andreas.searchFilesDeleteSelected()`
39+
Delete selected Delete selected files in search result.
3440

3541
### Edit commands
3642

package-lock.json

Lines changed: 26 additions & 98 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "andreas-talon",
33
"displayName": "Andreas Talon",
44
"description": "VSCode extension used by Talon Voice",
5-
"version": "3.75.1",
5+
"version": "3.76.0",
66
"publisher": "AndreasArvidsson",
77
"license": "MIT",
88
"main": "./out/extension.js",
@@ -75,6 +75,12 @@
7575
"extensions": [
7676
".snippet"
7777
]
78+
},
79+
{
80+
"id": "search-results",
81+
"aliases": [
82+
"Search results"
83+
]
7884
}
7985
],
8086
"grammars": [
@@ -161,6 +167,21 @@
161167
"category": "Andreas",
162168
"title": "Format"
163169
},
170+
{
171+
"command": "andreas.searchFiles",
172+
"category": "Andreas",
173+
"title": "Search files"
174+
},
175+
{
176+
"command": "andreas.searchFilesOpenSelected",
177+
"category": "Andreas",
178+
"title": "Open selected"
179+
},
180+
{
181+
"command": "andreas.searchFilesDeleteSelected",
182+
"category": "Andreas",
183+
"title": "Delete selected"
184+
},
164185
{
165186
"command": "andreas.generateRange",
166187
"category": "Andreas",
@@ -331,6 +352,7 @@
331352
"pokey.parse-tree"
332353
],
333354
"dependencies": {
355+
"glob": "11.0.2",
334356
"ignore": "7.0.4",
335357
"prettier": "3.5.3",
336358
"talon-snippets": "1.3.0"
@@ -347,7 +369,6 @@
347369
"eslint": "9.27.0",
348370
"file-updater": "0.5.0",
349371
"fs-extra": "11.3.0",
350-
"glob": "11.0.2",
351372
"mocha": "11.5.0",
352373
"ts-archetype": "0.5.0",
353374
"tsx": "4.19.4",

src/commands/commands.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ export const commandDescriptions = {
6464
"Format",
6565
"selected files. Used by file explorer context menu.",
6666
),
67+
searchFiles: visible("File", "Search files", "Search files in workspace.", "(query?: string)"),
68+
searchFilesOpenSelected: visible(
69+
"File",
70+
"Open selected",
71+
"Open selected files in search result.",
72+
),
73+
searchFilesDeleteSelected: visible(
74+
"File",
75+
"Delete selected",
76+
"Delete selected files in search result.",
77+
),
6778

6879
// Edit commands
6980
generateRange: visible(

src/commands/files/duplicateFile.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import path from "node:path";
2-
import { Uri } from "vscode";
3-
import { copyFile, openTextDocument } from "../../util/fileSystem";
1+
import * as path from "node:path";
2+
import * as vscode from "vscode";
3+
import { copyFile } from "../../util/fileSystem";
44
import { getActiveFileSchemaEditor } from "../../util/getActiveEditor";
55
import { getNewFilenameContext } from "../../util/getRenameContext";
66
import { showNewNameInputBox } from "../../util/showNewNameInputBox";
@@ -19,8 +19,8 @@ export async function duplicateFile(name?: string): Promise<void> {
1919
const filename = await showNewNameInputBox(suggestedName, suggestedExt);
2020

2121
if (filename) {
22-
const uri = Uri.file(path.join(context.dir, filename));
22+
const uri = vscode.Uri.file(path.join(context.dir, filename));
2323
await copyFile(context.uri, uri);
24-
await openTextDocument(uri);
24+
await vscode.window.showTextDocument(uri);
2525
}
2626
}

src/commands/files/newFile.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import path from "node:path";
2-
import { Uri, commands, window } from "vscode";
3-
import { showNewNameInputBox } from "../../util/showNewNameInputBox";
4-
import { createFile, openTextDocument } from "../../util/fileSystem";
1+
import * as path from "node:path";
2+
import * as vscode from "vscode";
3+
import { createFile } from "../../util/fileSystem";
54
import { getNewFilenameContext } from "../../util/getRenameContext";
5+
import { showNewNameInputBox } from "../../util/showNewNameInputBox";
66

77
export async function newFile(name?: string): Promise<void> {
8-
const editor = window.activeTextEditor;
8+
const editor = vscode.window.activeTextEditor;
99
const context = editor != null ? getNewFilenameContext(editor, name) : undefined;
1010

1111
if (context == null) {
12-
await commands.executeCommand("explorer.newFile");
12+
await vscode.commands.executeCommand("explorer.newFile");
1313
return;
1414
}
1515

@@ -20,8 +20,8 @@ export async function newFile(name?: string): Promise<void> {
2020
const filename = await showNewNameInputBox(suggestedName, suggestedExt);
2121

2222
if (filename) {
23-
const uri = Uri.file(path.join(context.dir, filename));
23+
const uri = vscode.Uri.file(path.join(context.dir, filename));
2424
await createFile(uri);
25-
await openTextDocument(uri);
25+
await vscode.window.showTextDocument(uri);
2626
}
2727
}

src/commands/files/removeFile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ export async function removeFile(): Promise<void> {
88
const filename = getFilename(uri);
99

1010
const remove = await window.showInformationMessage(
11-
`Are you sure you want to remove '${filename}'?`,
11+
`Are you sure you want to delete '${filename}'?`,
1212
{ modal: true },
13-
"Remove file",
13+
"Move to Recycle Bin",
1414
);
1515

1616
if (remove) {

src/commands/files/searchFiles.ts

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { glob } from "glob";
2+
import * as path from "node:path";
3+
import * as vscode from "vscode";
4+
import { deleteFile } from "../../util/fileSystem";
5+
import { getActiveEditor } from "../../util/getActiveEditor";
6+
import { getFullCommand } from "../../util/getFullCommand";
7+
8+
interface Link {
9+
range: vscode.Range;
10+
uri: vscode.Uri;
11+
selected: boolean;
12+
}
13+
14+
const divider = "----------------------------------------";
15+
const languageId = "search-results";
16+
const openLink = "[Open files]";
17+
const deleteLink = "[Delete files]";
18+
19+
export async function searchFiles(query?: string) {
20+
const lines: string[] = [];
21+
22+
if (!query) {
23+
query = await getQuery();
24+
if (!query) {
25+
return;
26+
}
27+
}
28+
29+
for (const ws of vscode.workspace.workspaceFolders ?? []) {
30+
if (lines.length > 0) {
31+
lines.push(divider, "");
32+
}
33+
lines.push(ws.name, "");
34+
35+
const files = await glob(`**/*${query}*`, {
36+
cwd: ws.uri.fsPath,
37+
dot: true,
38+
posix: true,
39+
});
40+
41+
for (const file of files.sort()) {
42+
lines.push(` ${file}`);
43+
}
44+
45+
lines.push("");
46+
}
47+
48+
lines.push(
49+
divider,
50+
"",
51+
openLink,
52+
"",
53+
deleteLink,
54+
"",
55+
"Select a file by adding a '*' or '-' prefix",
56+
"",
57+
);
58+
59+
const document = await vscode.workspace.openTextDocument({
60+
content: lines.join("\n"),
61+
language: languageId,
62+
});
63+
64+
await vscode.window.showTextDocument(document);
65+
}
66+
67+
export function searchFilesOpenSelected() {
68+
return Promise.all(
69+
getSelectedLinks().map((link) =>
70+
vscode.window.showTextDocument(link.uri, { preview: false }),
71+
),
72+
);
73+
}
74+
75+
export async function searchFilesDeleteSelected() {
76+
const selectedLinks = getSelectedLinks();
77+
78+
const remove =
79+
selectedLinks.length > 0 &&
80+
(await vscode.window.showInformationMessage(
81+
`Are you sure you want to delete ${selectedLinks.length} files?`,
82+
{ modal: true },
83+
"Move to Recycle Bin",
84+
));
85+
86+
if (remove) {
87+
await Promise.all(selectedLinks.map((link) => deleteFile(link.uri)));
88+
}
89+
}
90+
91+
export function registerSearchResults(): vscode.Disposable {
92+
return vscode.languages.registerDocumentLinkProvider(
93+
{ language: languageId },
94+
{
95+
provideDocumentLinks(document: vscode.TextDocument): vscode.DocumentLink[] {
96+
return parseDocument(document).map(
97+
(link) => new vscode.DocumentLink(link.range, link.uri),
98+
);
99+
},
100+
},
101+
);
102+
}
103+
104+
function getSelectedLinks() {
105+
return parseDocument(getActiveEditor().document).filter((link) => link.selected);
106+
}
107+
108+
function parseDocument(document: vscode.TextDocument): Link[] {
109+
if (document.languageId !== languageId) {
110+
console.error("Active document is not a search result");
111+
return [];
112+
}
113+
114+
const links: Link[] = [];
115+
const wsTexts = document.getText().split(divider + "\n");
116+
let lineNumber = 0;
117+
let hasSelectedLink = false;
118+
119+
wsTexts.forEach((wsText, index) => {
120+
const lines = wsText.split("\n");
121+
const wsName = lines[0];
122+
const wsPath = vscode.workspace.workspaceFolders?.find((ws) => ws.name === wsName)?.uri
123+
.fsPath;
124+
125+
if (index === wsTexts.length - 1) {
126+
if (!hasSelectedLink) {
127+
return;
128+
}
129+
130+
for (const lineText of lines) {
131+
const range = new vscode.Range(lineNumber, 0, lineNumber, 0 + lineText.length);
132+
133+
lineNumber++;
134+
135+
if (lineText.trim() === "") {
136+
continue;
137+
}
138+
139+
const command = (() => {
140+
if (lineText === openLink) {
141+
return "searchFilesOpenSelected";
142+
}
143+
if (lineText === deleteLink) {
144+
return "searchFilesDeleteSelected";
145+
}
146+
return null;
147+
})();
148+
149+
if (command == null) {
150+
continue;
151+
}
152+
153+
const uri = vscode.Uri.parse(`command:${getFullCommand(command)}`);
154+
155+
links.push({ range, uri, selected: false });
156+
}
157+
158+
return;
159+
}
160+
161+
if (wsPath == null || lines.length < 2) {
162+
lineNumber += lines.length + 1;
163+
return;
164+
}
165+
166+
lineNumber++;
167+
168+
for (let i = 1; i < lines.length; i++) {
169+
const lineText = lines[i];
170+
const match = lineText.match(/^\s*([-*]\s+)?/);
171+
const offset = match?.[0]?.length ?? 0;
172+
const selected = match?.[1] != null;
173+
const relativePath = lineText.slice(offset).trimEnd();
174+
175+
const range = new vscode.Range(
176+
lineNumber,
177+
offset,
178+
lineNumber,
179+
offset + relativePath.length,
180+
);
181+
182+
lineNumber++;
183+
184+
if (relativePath === "") {
185+
continue;
186+
}
187+
188+
const absPath = path.resolve(wsPath, relativePath);
189+
const uri = vscode.Uri.file(absPath);
190+
191+
if (selected) {
192+
hasSelectedLink = true;
193+
}
194+
195+
links.push({ range, uri, selected });
196+
}
197+
});
198+
199+
return links;
200+
}
201+
202+
async function getQuery(): Promise<string | undefined> {
203+
const query = await vscode.window.showInputBox({
204+
prompt: "Search query",
205+
placeHolder: "Search query",
206+
ignoreFocusOut: true,
207+
validateInput: (value) => {
208+
if (value.trim()) {
209+
return null;
210+
}
211+
return "Can't be empty";
212+
},
213+
});
214+
return query ? query.trim() : undefined;
215+
}

src/commands/registerCommands.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import { moveFile } from "./files/moveFile";
1313
import { newFile } from "./files/newFile";
1414
import { removeFile } from "./files/removeFile";
1515
import { renameFile } from "./files/renameFile";
16+
import {
17+
searchFiles,
18+
searchFilesDeleteSelected,
19+
searchFilesOpenSelected,
20+
} from "./files/searchFiles";
1621
import { focusTab } from "./focusTab";
1722
import { formatAllComments, formatComments } from "./formatComments/formatComments";
1823
import { formatSelectedFiles, formatWorkspaceFiles } from "./formatFiles";
@@ -45,6 +50,9 @@ export function registerCommands(
4550
moveFile,
4651
formatWorkspaceFiles,
4752
formatSelectedFiles,
53+
searchFiles,
54+
searchFilesOpenSelected,
55+
searchFilesDeleteSelected,
4856
// Edits
4957
generateRange,
5058
increment,

0 commit comments

Comments
 (0)