From 612864e1febb455cd806dca88c54c357d90cd4a1 Mon Sep 17 00:00:00 2001 From: subhajitlucky Date: Wed, 13 May 2026 08:46:56 +0530 Subject: [PATCH 1/2] feat: remember search settings --- src/WebviewManager.ts | 21 ++++++++++++-- src/extension.ts | 8 ++++-- src/types.ts | 10 +++++++ src/webview/script.ts | 67 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/WebviewManager.ts b/src/WebviewManager.ts index e2d7a02..cc05e0a 100644 --- a/src/WebviewManager.ts +++ b/src/WebviewManager.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { WebviewMessage } from './types'; +import { SearchState, WebviewMessage } from './types'; import { SearchService } from './services/SearchService'; import { FileService } from './services/FileService'; import { SyntaxHighlightService } from './services/SyntaxHighlightService'; @@ -16,6 +16,7 @@ export class WebviewManager { private syntaxHighlightService: SyntaxHighlightService; private disposables: vscode.Disposable[] = []; private panelCounter: number = 0; + private lastSearchState: SearchState | undefined; constructor(private context: vscode.ExtensionContext) { this.searchService = new SearchService(); @@ -37,7 +38,7 @@ export class WebviewManager { return; } - this.createPanel(); + this.createPanel(true); } showNewTab(initialSearchText?: string): void { @@ -80,7 +81,7 @@ export class WebviewManager { this.syntaxHighlightService.dispose(); } - private createPanel(): string { + private createPanel(restoreLastState: boolean = false): string { this.panelCounter++; const panelId = `search-${this.panelCounter}`; const tabNumber = this.panels.size + 1; @@ -129,6 +130,16 @@ export class WebviewManager { }); this.setupMessageHandler(panelId, panel); this.setupPanelDisposal(panelId, panel); + + if (restoreLastState && this.lastSearchState) { + setTimeout(() => { + panel.webview.postMessage({ + command: 'restoreSearchState', + state: this.lastSearchState + }); + }, 100); + } + return panelId; } @@ -156,6 +167,10 @@ export class WebviewManager { } break; + case 'updateSearchState': + this.lastSearchState = message.state; + break; + case 'getFileContent': if (message.filePath) { await this.handleGetFileContent(panel, message.filePath); diff --git a/src/extension.ts b/src/extension.ts index 77368cc..d7259ea 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,8 +44,12 @@ export function activate(context: vscode.ExtensionContext): void { } } - // Fallback: open normal search tab (potentially with selected text) - webviewManager?.showNewTab(selectedText); + // Fallback: selected text starts a fresh search; otherwise return to the last search. + if (selectedText) { + webviewManager?.showNewTab(selectedText); + } else { + webviewManager?.show(); + } } ); diff --git a/src/types.ts b/src/types.ts index 12546a0..4aad014 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,11 +36,21 @@ export interface FileSearchResult { icon?: ResolvedIconDefinition; } +export interface SearchState { + text: string; + fileMask: string; + scope: 'project' | 'directory'; + scopePath: string; +} + export type WebviewMessage = { command: 'search' text: string; includePattern?: string; excludePattern?: string; +} | { + command: 'updateSearchState' + state: SearchState; } | { command: 'getFileContent' filePath: string; diff --git a/src/webview/script.ts b/src/webview/script.ts index f08a1c2..3d8813f 100644 --- a/src/webview/script.ts +++ b/src/webview/script.ts @@ -1,6 +1,6 @@ /// -import { FileSearchResult, SearchMatch, WebviewMessage } from "../types"; +import { FileSearchResult, SearchMatch, SearchState, WebviewMessage } from "../types"; type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResult['icon'] }; @@ -70,9 +70,28 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul case 'setInitialSearchText': handleSetInitialSearchText(message.text); break; + case 'restoreSearchState': + handleRestoreSearchState(message.state); + break; } }); + function getSearchState(): SearchState { + return { + text: (searchInput as HTMLInputElement).value.trim(), + fileMask, + scope: currentScope === 'directory' ? 'directory' : 'project', + scopePath + }; + } + + function persistSearchState() { + postMessage({ + command: 'updateSearchState', + state: getSearchState() + }); + } + /** * Converts directory paths to glob patterns for file filtering. * Supports comma-separated paths and preserves existing glob patterns. @@ -105,6 +124,7 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul function performSearch() { const searchText = (searchInput as HTMLInputElement).value.trim(); + persistSearchState(); if (!searchText) { clearResults(); @@ -174,6 +194,7 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul filterToggleButton.classList.add('active'); fileMaskInput.focus(); } + persistSearchState(); }); fileMaskInput.addEventListener('input', () => { @@ -476,18 +497,27 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul performSearch(); } - function handleSetInitialDirectory(path: string, searchText?: string) { - // Switch to directory scope - scopeButtons.forEach(btn => btn.classList.remove('active')); - const directoryButton = document.querySelector('[data-scope="directory"]'); - if (directoryButton) { - directoryButton.classList.add('active'); + function applyScope(scope: string) { + scopeButtons.forEach(btn => { + btn.classList.toggle('active', btn.getAttribute('data-scope') === scope); + }); + + currentScope = scope === 'directory' ? 'directory' : 'project'; + + if (currentScope === 'directory') { + scopeInputContainer.style.display = 'flex'; + } else { + scopeInputContainer.style.display = 'none'; + scopePath = ''; + scopePathInput.value = ''; } + } - currentScope = 'directory'; + function handleSetInitialDirectory(path: string, searchText?: string) { + // Switch to directory scope + applyScope('directory'); scopePath = path; scopePathInput.value = path; - scopeInputContainer.style.display = 'flex'; // Set initial search text if provided if (searchText) { @@ -501,6 +531,23 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul performSearch(); } + function handleRestoreSearchState(state: SearchState) { + (searchInput as HTMLInputElement).value = state.text; + + fileMask = state.fileMask; + fileMaskInput.value = state.fileMask; + filterContainer.style.display = state.fileMask ? 'flex' : 'none'; + filterToggleButton.classList.toggle('active', Boolean(state.fileMask)); + + applyScope(state.scope); + if (state.scope === 'directory') { + scopePath = state.scopePath; + scopePathInput.value = state.scopePath; + } + + performSearch(); + } + function displayFilePreview(filePath: string, lineNumber: number, columnNumber: number) { const cached = fileContentsCache[filePath]; if (!cached) return; @@ -701,4 +748,4 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul }); searchInput.focus(); -}()); \ No newline at end of file +}()); From 844b8c985427f1c1946d2b44caea5302a1801112 Mon Sep 17 00:00:00 2001 From: subhajitlucky Date: Wed, 13 May 2026 09:00:43 +0530 Subject: [PATCH 2/2] fix: debounce search state persistence --- src/webview/script.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/webview/script.ts b/src/webview/script.ts index 3d8813f..2cb882f 100644 --- a/src/webview/script.ts +++ b/src/webview/script.ts @@ -42,6 +42,7 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul } } = {}; let searchTimeout: any = null; + let persistSearchStateTimeout: any = null; window.addEventListener('message', (event) => { const message = event.data; @@ -86,12 +87,28 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul } function persistSearchState() { + if (persistSearchStateTimeout) { + clearTimeout(persistSearchStateTimeout); + persistSearchStateTimeout = null; + } + postMessage({ command: 'updateSearchState', state: getSearchState() }); } + function schedulePersistSearchState() { + if (persistSearchStateTimeout) { + clearTimeout(persistSearchStateTimeout); + } + + persistSearchStateTimeout = setTimeout(() => { + persistSearchStateTimeout = null; + persistSearchState(); + }, 250); + } + /** * Converts directory paths to glob patterns for file filtering. * Supports comma-separated paths and preserves existing glob patterns. @@ -124,7 +141,7 @@ type SearchMatchWithId = SearchMatch & { matchId: number, icon?: FileSearchResul function performSearch() { const searchText = (searchInput as HTMLInputElement).value.trim(); - persistSearchState(); + schedulePersistSearchState(); if (!searchText) { clearResults();