Skip to content

Commit 8557658

Browse files
committed
Adding the grep search tool and file search tool.
1. Implemented the grep search tool using https://github.com/microsoft/vscode-ripgrep. 2. The grepped result show as clickable file path. 3. Implemented the file search tool. 3. The tool currently is disable in current pr, once grepSearch tool tested, will update the tool_index.json to enable this tool.
1 parent 8cca827 commit 8557658

File tree

12 files changed

+1040
-8
lines changed

12 files changed

+1040
-8
lines changed

.github/workflows/node.js.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ jobs:
132132
NODE_OPTIONS: '--max-old-space-size=8192'
133133
AWS_TOOLKIT_TEST_CACHE_DIR: '/tmp/.vscode-test/'
134134
AWS_TOOLKIT_TEST_USER_DIR: '/tmp/.vscode-test/user-data/'
135+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135136
steps:
136137
- uses: actions/checkout@v4
137138
- name: Use Node.js ${{ matrix.node-version }}
@@ -169,6 +170,7 @@ jobs:
169170
NODE_OPTIONS: '--max-old-space-size=8192'
170171
AWS_TOOLKIT_TEST_CACHE_DIR: '/tmp/.vscode-test/'
171172
AWS_TOOLKIT_TEST_USER_DIR: '/tmp/.vscode-test/user-data/'
173+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
172174
steps:
173175
- uses: actions/checkout@v4
174176
- name: Use Node.js ${{ matrix.node-version }}

buildspec/linuxTests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ env:
1313
# followed by Error: Could not delete obsolete instance handle Error: ENOENT: no such file or directory, unlink <path>
1414
AWS_TOOLKIT_TEST_CACHE_DIR: '/tmp/.vscode-test/'
1515
AWS_TOOLKIT_TEST_USER_DIR: '/tmp/.vscode-test/user-data/'
16+
VSCODE_RIPGREP_TOKEN: ${GITHUB_READONLY_TOKEN}
1617

1718
phases:
1819
install:

buildspec/packageTestVsix.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ env:
88
AWS_TOOLKIT_TEST_USER_DIR: '/tmp/'
99
# needed or else webpack will cause it to run out of memory
1010
NODE_OPTIONS: '--max-old-space-size=8192'
11+
# VSCODE_RIPGREP_TOKEN will be set in pre_build phase
1112

1213
phases:
1314
install:
@@ -20,6 +21,9 @@ phases:
2021
pre_build:
2122
commands:
2223
- export HOME=/home/codebuild-user
24+
# Set up VSCODE_RIPGREP_TOKEN for GitHub API access
25+
- export VSCODE_RIPGREP_TOKEN=${GITHUB_READONLY_TOKEN}
26+
- echo "Setting up VSCODE_RIPGREP_TOKEN for GitHub API access"
2327
- bash buildspec/shared/linux-pre_build.sh
2428

2529
build:

package-lock.json

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

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"dependencies": {
7676
"@types/node": "^22.7.5",
7777
"vscode-nls": "^5.2.0",
78-
"vscode-nls-dev": "^4.0.4"
78+
"vscode-nls-dev": "^4.0.4",
79+
"@vscode/ripgrep": "1.15.11"
7980
}
8081
}

packages/amazonq/scripts/build/copyFiles.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ const tasks: CopyTask[] = [
6060
target: path.join('../../node_modules', 'web-tree-sitter', 'tree-sitter.wasm'),
6161
destination: path.join('src', 'tree-sitter.wasm'),
6262
},
63+
// ripgrep binary
64+
{
65+
target: path.join('../../node_modules', '@vscode/ripgrep', 'bin'),
66+
destination: 'bin/',
67+
},
6368
]
6469

6570
function copy(task: CopyTask): void {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
import { getLogger } from '../../shared/logger/logger'
7+
import { readDirectoryRecursively } from '../../shared/utilities/workspaceUtils'
8+
import fs from '../../shared/fs/fs'
9+
import { Writable } from 'stream'
10+
import path from 'path'
11+
import { InvokeOutput, OutputKind, sanitizePath, CommandValidation } from './toolShared'
12+
import { isInDirectory } from '../../shared/filesystemUtilities'
13+
14+
export interface FileSearchParams {
15+
path: string
16+
pattern: string
17+
maxDepth?: number
18+
caseSensitive?: boolean
19+
}
20+
21+
export class FileSearch {
22+
private fsPath: string
23+
private pattern: RegExp
24+
private maxDepth?: number
25+
private readonly logger = getLogger('fileSearch')
26+
27+
constructor(params: FileSearchParams) {
28+
this.fsPath = params.path
29+
// Create RegExp with case sensitivity option
30+
this.pattern = new RegExp(params.pattern, params.caseSensitive ? '' : 'i')
31+
this.maxDepth = params.maxDepth
32+
}
33+
34+
public async validate(): Promise<void> {
35+
if (!this.fsPath || this.fsPath.trim().length === 0) {
36+
throw new Error('Path cannot be empty.')
37+
}
38+
if (this.maxDepth !== undefined && this.maxDepth < 0) {
39+
throw new Error('MaxDepth cannot be negative.')
40+
}
41+
42+
const sanitized = sanitizePath(this.fsPath)
43+
this.fsPath = sanitized
44+
45+
const pathUri = vscode.Uri.file(this.fsPath)
46+
let pathExists: boolean
47+
try {
48+
pathExists = await fs.existsDir(pathUri)
49+
if (!pathExists) {
50+
throw new Error(`Path: "${this.fsPath}" does not exist or cannot be accessed.`)
51+
}
52+
} catch (err) {
53+
throw new Error(`Path: "${this.fsPath}" does not exist or cannot be accessed. (${err})`)
54+
}
55+
}
56+
57+
public queueDescription(updates: Writable): void {
58+
const fileName = path.basename(this.fsPath)
59+
if (this.maxDepth === undefined) {
60+
updates.write(`Searching for files matching pattern: ${this.pattern} in ${fileName} recursively`)
61+
} else if (this.maxDepth === 0) {
62+
updates.write(`Searching for files matching pattern: ${this.pattern} in ${fileName}`)
63+
} else {
64+
const level = this.maxDepth > 1 ? 'levels' : 'level'
65+
updates.write(
66+
`Searching for files matching pattern: ${this.pattern} in ${fileName} limited to ${this.maxDepth} subfolder ${level}`
67+
)
68+
}
69+
updates.end()
70+
}
71+
72+
public requiresAcceptance(): CommandValidation {
73+
const workspaceFolders = vscode.workspace.workspaceFolders
74+
if (!workspaceFolders || workspaceFolders.length === 0) {
75+
return { requiresAcceptance: true }
76+
}
77+
const isInWorkspace = workspaceFolders.some((folder) => isInDirectory(folder.uri.fsPath, this.fsPath))
78+
if (!isInWorkspace) {
79+
return { requiresAcceptance: true }
80+
}
81+
return { requiresAcceptance: false }
82+
}
83+
84+
public async invoke(updates?: Writable): Promise<InvokeOutput> {
85+
try {
86+
const fileUri = vscode.Uri.file(this.fsPath)
87+
const allFiles = await readDirectoryRecursively(fileUri, this.maxDepth)
88+
89+
// Filter files by regex pattern
90+
const matchedFiles = allFiles.filter((filePath) => {
91+
// Extract just the filename from the path
92+
const fileName = path.basename(filePath.split(' ').slice(1).join(' '))
93+
return this.pattern.test(fileName)
94+
})
95+
96+
return this.createOutput(matchedFiles.join('\n'))
97+
} catch (error: any) {
98+
this.logger.error(`Failed to search files in "${this.fsPath}": ${error.message || error}`)
99+
throw new Error(`Failed to search files in "${this.fsPath}": ${error.message || error}`)
100+
}
101+
}
102+
103+
private createOutput(content: string): InvokeOutput {
104+
return {
105+
output: {
106+
kind: OutputKind.Text,
107+
content: content,
108+
},
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)