Skip to content

Show autocomplete suggestions for File Query Filters on Obsidian App #1128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 102 additions & 7 deletions src/interface/obsidian/src/search_modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
currentController: AbortController | null = null; // To cancel requests
isLoading: boolean = false;
loadingEl: HTMLElement;
private isFileFilterMode: boolean = false;
private fileSelected: string = "";
private allFiles: Array<{path: string, inVault: boolean}> = [];
private resultsTitle: HTMLDivElement;

constructor(app: App, setting: KhojSetting, find_similar_notes: boolean = false) {
super(app);
Expand Down Expand Up @@ -85,6 +89,44 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {

// Set Placeholder Text for Modal
this.setPlaceholder('Search with Khoj...');

// Initialize allFiles with files in valut
this.allFiles = this.app.vault.getFiles().map(file => ({
path: file.path,
inVault: true
}));

// Update isFileFilterMode when input changes
this.inputEl.addEventListener('input', () => {
const fileFilterMatch = this.inputEl.value.match(/file:([^"\s]*|"[^"]*")?/);
// Reset fileSelected when input no longer matches file filter pattern
if (!fileFilterMatch) {
this.fileSelected = "";
}
// Set isFileFilterMode when file filter pattern is detected and no file has been selected yet
if (!this.fileSelected && !this.isFileFilterMode && fileFilterMatch) {
this.isFileFilterMode = true;
}
});

// Override the default selectSuggestion method
this.selectSuggestion = async (value: SearchResult & { inVault: boolean }, evt: MouseEvent | KeyboardEvent) => {
if (this.isFileFilterMode) {
await this.onChooseSuggestion(value, evt);
} else {
// Close only for non-file-filter mode
this.close();
await this.onChooseSuggestion(value, evt);
}
};

// Add title element
this.resultsTitle = createDiv();
this.resultsTitle.style.padding = "8px";
this.resultsTitle.style.fontWeight = "bold";

// Insert title before results container
this.resultContainerEl.parentElement?.insertBefore(this.resultsTitle, this.resultContainerEl);
}

// Check if the file exists in the vault
Expand All @@ -99,7 +141,30 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
}

async getSuggestions(query: string): Promise<SearchResult[]> {
// Do not show loading if the query is empty
// Check if we are in file filter mode and input matches file filter pattern
const fileFilterMatch = query.match(/file:([^,]*)?$/);
if (this.isFileFilterMode && fileFilterMatch) {
const partialPath = fileFilterMatch[1] || '';
// Update title for file filter mode
this.resultsTitle.setText("Select a file:");
// Return filtered file suggestions
return this.allFiles
.filter(file => file.path.toLowerCase().includes(partialPath.toLowerCase().trim()))
.map(file => ({
entry: file.path,
file: file.path,
inVault: file.inVault
}));
}

// Update title for search results
if (query.trim()) {
this.resultsTitle.setText("Search results:");
} else {
this.resultsTitle.setText("");
}

// If not in file filter mode, continue with normal search
if (!query.trim()) {
this.isLoading = false;
this.updateLoadingState();
Expand Down Expand Up @@ -138,22 +203,29 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {

const data = await response.json();

// Parse search results
// Parse search results and update allFiles with any new non-vault files
let results = data
.filter((result: any) =>
!this.find_similar_notes || !result.additional.file.endsWith(this.app.workspace.getActiveFile()?.path)
)
.map((result: any) => {
const isInVault = this.isFileInVault(result.additional.file);

// Add new non-vault files to allFiles if they don't exist
if (!this.allFiles.some(file => file.path === result.additional.file)) {
this.allFiles.push({
path: result.additional.file,
inVault: isInVault
});
}

return {
entry: result.entry,
file: result.additional.file,
inVault: this.isFileInVault(result.additional.file)
inVault: isInVault
} as SearchResult & { inVault: boolean };
})
.sort((a: SearchResult & { inVault: boolean }, b: SearchResult & { inVault: boolean }) => {
if (a.inVault === b.inVault) return 0;
return a.inVault ? -1 : 1;
});
.sort((a: SearchResult & { inVault: boolean }, b: SearchResult & { inVault: boolean }) => Number(b.inVault) - Number(a.inVault));

this.query = query;

Expand Down Expand Up @@ -203,6 +275,15 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
}

async renderSuggestion(result: SearchResult & { inVault: boolean }, el: HTMLElement) {
if (this.isFileFilterMode) {
// Render file suggestions
el.createEl("div", {
text: result.entry,
cls: "khoj-file-suggestion"
});
return;
}

// Max number of lines to render
let lines_to_render = 8;

Expand Down Expand Up @@ -251,6 +332,20 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
}

async onChooseSuggestion(result: SearchResult & { inVault: boolean }, _: MouseEvent | KeyboardEvent) {
if (this.isFileFilterMode) {
// When a file suggestion is selected, append it to the current input
const currentValue = this.inputEl.value;
const beforeFile = currentValue.substring(0, currentValue.lastIndexOf('file:'));
this.inputEl.value = `${beforeFile}file:"${result.entry}"`;
// Set fileSelected to the selected file
this.fileSelected = result.entry;
// Reset isFileFilterMode when a file is selected
this.isFileFilterMode = false;
// Trigger input event to refresh suggestions
this.inputEl.dispatchEvent(new Event('input'));
return;
}

// Only open files that are in the vault
if (!result.inVault) {
new Notice("This file is not in your vault");
Expand Down
5 changes: 5 additions & 0 deletions src/interface/obsidian/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -859,3 +859,8 @@ img.copy-icon {
transform: rotate(360deg);
}
}

.khoj-file-suggestion {
padding: 8px;
color: var(--text-muted);
}