Skip to content

Commit bbb1805

Browse files
committed
glob search working
1 parent 67888c7 commit bbb1805

File tree

2 files changed

+195
-91
lines changed

2 files changed

+195
-91
lines changed

extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IntelliJIde.kt

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import com.github.continuedev.continueintellijextension.utils.*
77
import com.intellij.codeInsight.daemon.impl.HighlightInfo
88
import com.intellij.execution.configurations.GeneralCommandLine
99
import com.intellij.execution.util.ExecUtil
10+
import com.intellij.find.FindModel
11+
import com.intellij.find.impl.FindInProjectUtil
1012
import com.intellij.ide.plugins.PluginManager
1113
import com.intellij.ide.plugins.PluginManagerCore
1214
import com.intellij.lang.annotation.HighlightSeverity
@@ -20,15 +22,19 @@ import com.intellij.openapi.editor.impl.DocumentMarkupModel
2022
import com.intellij.openapi.extensions.PluginId
2123
import com.intellij.openapi.fileEditor.FileDocumentManager
2224
import com.intellij.openapi.fileEditor.FileEditorManager
25+
import com.intellij.openapi.progress.EmptyProgressIndicator
2326
import com.intellij.openapi.project.Project
2427
import com.intellij.openapi.project.guessProjectDir
28+
import com.intellij.openapi.roots.ProjectFileIndex
2529
import com.intellij.openapi.util.IconLoader
2630
import com.intellij.openapi.vfs.LocalFileSystem
2731
import com.intellij.openapi.vfs.VirtualFileManager
2832
import com.intellij.psi.PsiDocumentManager
2933
import com.intellij.psi.search.FilenameIndex
3034
import com.intellij.psi.search.GlobalSearchScope
3135
import com.intellij.testFramework.LightVirtualFile
36+
import com.intellij.usages.FindUsagesProcessPresentation
37+
import com.intellij.usages.UsageViewPresentation
3238
import kotlinx.coroutines.*
3339
import java.awt.Toolkit
3440
import java.awt.datatransfer.DataFlavor
@@ -327,58 +333,67 @@ class IntelliJIDE(
327333
return listOf("")
328334
}
329335
override suspend fun getSearchResults(query: String): String {
330-
// val findModel = FindModel().apply {
331-
// stringToFind = searchText
332-
// isRegularExpressions = false
333-
// isWholeWordsOnly = false
334-
// isCaseSensitive = false
335-
// searchContext = FindModel.SearchContext.ANY
336-
// isGlobal = true
337-
// }
338-
339-
// val findManager = FindManager.getInstance(project)
340-
// val scope = GlobalSearchScope.projectScope(project)
341-
342-
// FindInProjectUtil.findUsages(
343-
// findModel,
344-
// project.basePath,
345-
// project,
346-
// {
347-
// // Process each found usage
348-
// val virtualFile = it.virtualFile
349-
// val lineNumber = it.line
350-
// // Handle result
351-
// },
352-
// scope
353-
// )
354-
355-
// return withContext(Dispatchers.IO) {
356-
// val findModel = FindModel().apply {
357-
// stringToFind = query
358-
// isRegularExpressions = false
359-
// isWholeWordsOnly = false
360-
// isCaseSensitive = false
361-
// searchContext = UsageSearchContext.ANY
362-
// }
336+
val ideInfo = this.getIdeInfo()
337+
if (ideInfo.remoteName != "local") {
338+
val command = GeneralCommandLine(
339+
ripgrep,
340+
"-i",
341+
"--ignore-file",
342+
".continueignore",
343+
"--ignore-file",
344+
".gitignore",
345+
"-C",
346+
"2",
347+
"--heading",
348+
"-e",
349+
query,
350+
"."
351+
)
363352

364-
// val results = StringBuilder()
365-
// runReadAction {
366-
// FindInProjectUtil.findUsages(
367-
// findModel,
368-
// project.baseDir,
369-
// {
370-
// results.append("${it.file.path}:${it.line + 1}: ${it.lineText}\n")
371-
// true
372-
// },
373-
// null
374-
// )
375-
// }
376-
// results.toString()
377-
// }
378-
val command = GeneralCommandLine(ripgrep, "-i", "-C", "2", "--heading", "-e", query, ".")
379-
command.setWorkDirectory(project.basePath)
380-
return ExecUtil.execAndGetOutput(command).stdout
381-
}
353+
command.setWorkDirectory(project.basePath)
354+
return ExecUtil.execAndGetOutput(command).stdout
355+
} else {
356+
return "Ripgrep not supported, this workspace is remote"
357+
// For remote workspaces, use JetBrains search functionality
358+
// val searchResults = StringBuilder()
359+
// ApplicationManager.getApplication().invokeAndWait {
360+
// val options = FindModel().apply {
361+
// stringToFind = query
362+
// isCaseSensitive = false
363+
// isRegularExpressions = false
364+
// isWholeWordsOnly = false
365+
// searchContext = FindModel.SearchContext.ANY // or IN_CODE, IN_COMMENTS, IN_STRING_LITERALS, etc.
366+
// isMultiline = true // Allow matching across multiple lines
367+
// }
368+
//
369+
// val progressIndicator = EmptyProgressIndicator()
370+
// val presentation = FindUsagesProcessPresentation(
371+
// UsageViewPresentation()
372+
// )
373+
// val filesToSearch = ProjectFileIndex.getInstance(project)
374+
// .iterateContent(::ArrayList)
375+
// .filterNot { it.isDirectory }
376+
// .toSet()
377+
//
378+
//
379+
// FindInProjectUtil.findUsages(
380+
// options,
381+
// project,
382+
// progressIndicator,
383+
// presentation,
384+
// filesToSearch
385+
// ) { result ->
386+
// val virtualFile = result.virtualFile
387+
// searchResults.append(virtualFile.path).append("\n")
388+
// searchResults.append("${result..trim()}\n")
389+
// true // continue searching
390+
// }
391+
// }
392+
// return searchResults.toString()
393+
//
394+
// val scope = GlobalSearchScope.projectScope(project)
395+
}
396+
}
382397

383398
override suspend fun subprocess(command: String, cwd: String?): List<Any> {
384399
val commandList = command.split(" ")

extensions/vscode/src/VsCodeIde.ts

Lines changed: 129 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -555,54 +555,143 @@ class VsCodeIde implements IDE {
555555
.map((t) => (t.input as vscode.TabInputText).uri.toString());
556556
}
557557

558+
runRipgrepQuery(dirUri: string, args: string[]) {
559+
const relativeDir = vscode.Uri.parse(dirUri).fsPath;
560+
const ripGrepUri = vscode.Uri.joinPath(
561+
getExtensionUri(),
562+
"out/node_modules/@vscode/ripgrep/bin/rg",
563+
);
564+
const p = child_process.spawn(ripGrepUri.fsPath, args, {
565+
cwd: relativeDir,
566+
});
567+
let output = "";
568+
569+
p.stdout.on("data", (data) => {
570+
output += data.toString();
571+
});
572+
573+
return new Promise<string>((resolve, reject) => {
574+
p.on("error", reject);
575+
p.on("close", (code) => {
576+
if (code === 0) {
577+
resolve(output);
578+
} else if (code === 1) {
579+
// No matches
580+
resolve("No matches found");
581+
} else {
582+
reject(new Error(`Process exited with code ${code}`));
583+
}
584+
});
585+
});
586+
}
587+
558588
async getFileResults(pattern: string): Promise<string[]> {
559-
// vscode.workspace.findFiles("");
560-
return ["file1", "file2"];
589+
const MAX_FILE_RESULTS = 200;
590+
if (vscode.env.remoteName) {
591+
// TODO better tests for this remote search implementation
592+
// throw new Error("Ripgrep not supported, this workspace is remote");
593+
594+
// IMPORTANT: findFiles automatically accounts for .gitignore
595+
const ignoreFiles = await vscode.workspace.findFiles(
596+
"**/.continueignore",
597+
null,
598+
);
599+
600+
const ignoreGlobs: Set<string> = new Set();
601+
for (const file of ignoreFiles) {
602+
const content = await vscode.workspace.fs.readFile(file);
603+
const filePath = vscode.workspace.asRelativePath(file);
604+
const fileDir = filePath
605+
.replace(/\\/g, "/")
606+
.replace(/\/$/, "")
607+
.split("/")
608+
.slice(0, -1)
609+
.join("/");
610+
611+
const patterns = Buffer.from(content)
612+
.toString()
613+
.split("\n")
614+
.map((line) => line.trim())
615+
.filter(
616+
(line) => line && !line.startsWith("#") && !pattern.startsWith("!"),
617+
);
618+
// VSCode does not support negations
619+
620+
patterns
621+
// Handle prefix
622+
.map((pattern) => {
623+
const normalizedPattern = pattern.replace(/\\/g, "/");
624+
625+
if (normalizedPattern.startsWith("/")) {
626+
if (fileDir) {
627+
return `{/,}${normalizedPattern}`;
628+
} else {
629+
return `${fileDir}/${normalizedPattern.substring(1)}`;
630+
}
631+
} else {
632+
if (fileDir) {
633+
return `${fileDir}/${normalizedPattern}`;
634+
} else {
635+
return `**/${normalizedPattern}`;
636+
}
637+
}
638+
})
639+
// Handle suffix
640+
.map((pattern) => {
641+
return pattern.endsWith("/") ? `${pattern}**/*` : pattern;
642+
})
643+
.forEach((pattern) => {
644+
ignoreGlobs.add(pattern);
645+
});
646+
}
647+
648+
const ignoreGlobsArray = Array.from(ignoreGlobs);
649+
650+
const results = await vscode.workspace.findFiles(
651+
pattern,
652+
ignoreGlobs.size ? `{${ignoreGlobsArray.join(",")}}` : null,
653+
MAX_FILE_RESULTS,
654+
);
655+
return results.map((result) => vscode.workspace.asRelativePath(result));
656+
} else {
657+
const results: string[] = [];
658+
for (const dir of await this.getWorkspaceDirs()) {
659+
const dirResults = await this.runRipgrepQuery(dir, [
660+
"--files",
661+
"--iglob",
662+
pattern,
663+
"--ignore-file",
664+
".continueignore",
665+
"--ignore-file",
666+
".gitignore",
667+
]);
668+
669+
results.push(dirResults);
670+
}
671+
672+
return results.join("\n").split("\n").slice(0, MAX_FILE_RESULTS);
673+
}
561674
}
562675

563676
async getSearchResults(query: string): Promise<string> {
564677
if (vscode.env.remoteName) {
565-
return "Ripgrep not supported, this workspace is remote";
678+
throw new Error("Ripgrep not supported, this workspace is remote");
566679
}
567680
const results: string[] = [];
568681
for (const dir of await this.getWorkspaceDirs()) {
569-
const relativeDir = vscode.Uri.parse(dir).fsPath;
570-
const ripGrepUri = vscode.Uri.joinPath(
571-
getExtensionUri(),
572-
"out/node_modules/@vscode/ripgrep/bin/rg",
573-
);
574-
const p = child_process.spawn(
575-
ripGrepUri.fsPath,
576-
[
577-
"-i", // Case-insensitive search
578-
"-C",
579-
"2", // Show 2 lines of context
580-
"--heading", // Only show filepath once per result
581-
"-e",
582-
query, // Pattern to search for
583-
".", // Directory to search in
584-
],
585-
{ cwd: relativeDir },
586-
);
587-
let output = "";
588-
589-
p.stdout.on("data", (data) => {
590-
output += data.toString();
591-
});
592-
593-
const dirResults = await new Promise<string>((resolve, reject) => {
594-
p.on("error", reject);
595-
p.on("close", (code) => {
596-
if (code === 0) {
597-
resolve(output);
598-
} else if (code === 1) {
599-
// No matches
600-
resolve("No matches found");
601-
} else {
602-
reject(new Error(`Process exited with code ${code}`));
603-
}
604-
});
605-
});
682+
const dirResults = await this.runRipgrepQuery(dir, [
683+
"-i", // Case-insensitive search
684+
"--ignore-file",
685+
".continueignore",
686+
"--ignore-file",
687+
".gitignore",
688+
"-C",
689+
"2", // Show 2 lines of context
690+
"--heading", // Only show filepath once per result
691+
"-e",
692+
query, // Pattern to search for
693+
".", // Directory to search in
694+
]);
606695

607696
results.push(dirResults);
608697
}

0 commit comments

Comments
 (0)