Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbab6c21

Browse files
committed
feat: Add search filter to sidebar
1 parenta75342a commitbab6c21

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

‎package.json‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,18 @@
212212
"title":"Coder: Open App Status",
213213
"icon":"$(robot)",
214214
"when":"coder.authenticated"
215+
},
216+
{
217+
"command":"coder.searchWorkspaces",
218+
"title":"Coder: Search Workspaces",
219+
"icon":"$(search)",
220+
"when":"coder.authenticated && coder.isOwner"
221+
},
222+
{
223+
"command":"coder.clearWorkspaceSearch",
224+
"title":"Coder: Clear Workspace Search",
225+
"icon":"$(clear-all)",
226+
"when":"coder.authenticated && coder.isOwner"
215227
}
216228
],
217229
"menus": {
@@ -239,6 +251,21 @@
239251
"command":"coder.refreshWorkspaces",
240252
"when":"coder.authenticated && view == myWorkspaces",
241253
"group":"navigation"
254+
},
255+
{
256+
"command":"coder.searchWorkspaces",
257+
"when":"coder.authenticated && coder.isOwner && view == allWorkspaces",
258+
"group":"navigation"
259+
},
260+
{
261+
"command":"coder.refreshWorkspaces",
262+
"when":"coder.authenticated && coder.isOwner && view == allWorkspaces",
263+
"group":"navigation"
264+
},
265+
{
266+
"command":"coder.clearWorkspaceSearch",
267+
"when":"coder.authenticated && coder.isOwner && view == allWorkspaces",
268+
"group":"navigation"
242269
}
243270
],
244271
"view/item/context": [

‎src/commands.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,53 @@ export class Commands {
684684
});
685685
}
686686

687+
/**
688+
* Search/filter workspaces in the All Workspaces view.
689+
* This method will be called from the view title menu.
690+
*/
691+
publicasyncsearchWorkspaces():Promise<void>{
692+
constquickPick=vscode.window.createQuickPick();
693+
quickPick.placeholder=
694+
"Type to search workspaces by name, owner, template, status, or agent";
695+
quickPick.title="Search All Workspaces";
696+
quickPick.value="";
697+
698+
// Get current search filter to show in the input
699+
constcurrentFilter=(awaitvscode.commands.executeCommand(
700+
"coder.getWorkspaceSearchFilter",
701+
))asstring;
702+
if(currentFilter){
703+
quickPick.value=currentFilter;
704+
}
705+
706+
quickPick.ignoreFocusOut=true;// Keep open when clicking elsewhere
707+
quickPick.canSelectMany=false;// Don't show selection list
708+
709+
quickPick.onDidChangeValue((value)=>{
710+
// Update the search filter in real-time as user types
711+
vscode.commands.executeCommand("coder.setWorkspaceSearchFilter",value);
712+
});
713+
714+
quickPick.onDidAccept(()=>{
715+
// When user presses Enter, close the search
716+
quickPick.hide();
717+
});
718+
719+
quickPick.onDidHide(()=>{
720+
// Don't clear the search when closed - keep the filter active
721+
quickPick.dispose();
722+
});
723+
724+
quickPick.show();
725+
}
726+
727+
/**
728+
* Clear the workspace search filter.
729+
*/
730+
publicclearWorkspaceSearch():void{
731+
vscode.commands.executeCommand("coder.setWorkspaceSearchFilter","");
732+
}
733+
687734
/**
688735
* Return agents from the workspace.
689736
*

‎src/extension.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,23 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
290290
"coder.viewLogs",
291291
commands.viewLogs.bind(commands),
292292
);
293+
vscode.commands.registerCommand(
294+
"coder.searchWorkspaces",
295+
commands.searchWorkspaces.bind(commands),
296+
);
297+
vscode.commands.registerCommand(
298+
"coder.setWorkspaceSearchFilter",
299+
(searchTerm:string)=>{
300+
allWorkspacesProvider.setSearchFilter(searchTerm);
301+
},
302+
);
303+
vscode.commands.registerCommand("coder.getWorkspaceSearchFilter",()=>{
304+
returnallWorkspacesProvider.getSearchFilter();
305+
});
306+
vscode.commands.registerCommand(
307+
"coder.clearWorkspaceSearch",
308+
commands.clearWorkspaceSearch.bind(commands),
309+
);
293310

294311
// Since the "onResolveRemoteAuthority:ssh-remote" activation event exists
295312
// in package.json we're able to perform actions before the authority is

‎src/workspacesProvider.ts‎

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class WorkspaceProvider
4242
privatetimeout:NodeJS.Timeout|undefined;
4343
privatefetching=false;
4444
privatevisible=false;
45+
privatesearchFilter="";
4546

4647
constructor(
4748
privatereadonlygetWorkspacesQuery:WorkspaceQuery,
@@ -52,6 +53,15 @@ export class WorkspaceProvider
5253
// No initialization.
5354
}
5455

56+
setSearchFilter(filter:string){
57+
this.searchFilter=filter;
58+
this.refresh(undefined);
59+
}
60+
61+
getSearchFilter():string{
62+
returnthis.searchFilter;
63+
}
64+
5565
// fetchAndRefresh fetches new workspaces, re-renders the entire tree, then
5666
// keeps refreshing (if a timer length was provided) as long as the user is
5767
// still logged in and no errors were encountered fetching workspaces.
@@ -300,7 +310,90 @@ export class WorkspaceProvider
300310

301311
returnPromise.resolve([]);
302312
}
303-
returnPromise.resolve(this.workspaces||[]);
313+
314+
// Filter workspaces based on search term
315+
letfilteredWorkspaces=this.workspaces||[];
316+
consttrimmedFilter=this.searchFilter.trim();
317+
if(trimmedFilter){
318+
constsearchTerm=trimmedFilter.toLowerCase();
319+
filteredWorkspaces=filteredWorkspaces.filter((workspace)=>
320+
this.matchesSearchTerm(workspace,searchTerm),
321+
);
322+
}
323+
324+
returnPromise.resolve(filteredWorkspaces);
325+
}
326+
327+
/**
328+
* Check if a workspace matches the given search term using smart search logic.
329+
* Prioritizes exact word matches over substring matches.
330+
*/
331+
privatematchesSearchTerm(
332+
workspace:WorkspaceTreeItem,
333+
searchTerm:string,
334+
):boolean{
335+
constworkspaceName=workspace.workspace.name.toLowerCase();
336+
constownerName=workspace.workspace.owner_name.toLowerCase();
337+
consttemplateName=(
338+
workspace.workspace.template_display_name||
339+
workspace.workspace.template_name||
340+
""
341+
).toLowerCase();
342+
conststatus=workspace.workspace.latest_build.status.toLowerCase();
343+
344+
// Check if any agent names match the search term
345+
constagents=extractAgents(workspace.workspace.latest_build.resources);
346+
constagentNames=agents.map((agent)=>agent.name.toLowerCase());
347+
consthasMatchingAgent=agentNames.some((agentName)=>
348+
agentName.includes(searchTerm),
349+
);
350+
351+
// Check if any agent metadata contains the search term
352+
consthasMatchingMetadata=agents.some((agent)=>{
353+
constwatcher=this.agentWatchers[agent.id];
354+
if(watcher?.metadata){
355+
returnwatcher.metadata.some((metadata)=>{
356+
constmetadataStr=JSON.stringify(metadata).toLowerCase();
357+
returnmetadataStr.includes(searchTerm);
358+
});
359+
}
360+
returnfalse;
361+
});
362+
363+
// Smart search: Try exact word match first, then fall back to substring
364+
constsearchWords=searchTerm
365+
.split(/\s+/)
366+
.filter((word)=>word.length>0);
367+
constallText=[
368+
workspaceName,
369+
ownerName,
370+
templateName,
371+
status,
372+
...agentNames,
373+
].join(" ");
374+
375+
// Check for exact word matches (higher priority)
376+
consthasExactWordMatch=
377+
searchWords.length>0&&
378+
searchWords.some((word)=>{
379+
// Escape special regex characters to prevent injection
380+
constescapedWord=word.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
381+
constwordBoundaryRegex=newRegExp(`\\b${escapedWord}\\b`,"i");
382+
returnwordBoundaryRegex.test(allText);
383+
});
384+
385+
// Check for substring matches (lower priority) - only if no exact word match
386+
consthasSubstringMatch=
387+
!hasExactWordMatch&&
388+
(workspaceName.includes(searchTerm)||
389+
ownerName.includes(searchTerm)||
390+
templateName.includes(searchTerm)||
391+
status.includes(searchTerm)||
392+
hasMatchingAgent||
393+
hasMatchingMetadata);
394+
395+
// Return true if either exact word match or substring match
396+
returnhasExactWordMatch||hasSubstringMatch;
304397
}
305398
}
306399

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp