Search in content of all open tabs (markdown files)

Hello friends!

First Showcase from me!


Use case

Ever encountered the problem of having 8-15 tabs of related content open – you can even save those as a Workspace and come back to it, right – and forgot which file was recently added to – where you would you like to go back to and refer to it…or build on it…

Sure, the Remember Cursor Position plugin can be helpful but you may have already moved in that file since and the position of the cursor was saved again…and you don’t remember which file it was or what it really was…

I tried looking for a solution on the forum and instead solved it for myself with some bot help…

It is somewhat rudimentary but can be helpful in itself.

Templater snippet

<%*
// Get the active workspace
const workspace = app.workspace;

// Get all open leaves (tabs)
const leaves = workspace.getLeavesOfType("markdown");

// Define the list of filenames to exclude
const excludeFilenames = [
    "FileModifiedFileList_Query",
    "fifty latest notes",
    "quicksearch"
];

// Extract filenames, filter out excluded filenames
const filenames = leaves
    .map(leaf => leaf.view.file.basename)
    .filter(filename => !excludeFilenames.includes(filename));

// Format the filenames for search modal
let formatted = `file: /^(${filenames.join("|")})\\.md/`;

// Copy the result to the clipboard
navigator.clipboard.writeText(formatted).then(() => {
    // Show a notice to the user
    new Notice(`Copied to clipboard:\n${formatted}`);
}).catch(err => {
    console.error('Could not copy text: ', err);
});
_%>

Save the snippet to any name, register it in Templater so you can add a hotkey to it.

Because all open tabs mean all open markdown files in any tab groups and sidebar areas, add any of your md files pinned to any sidebars in the // Define the list of filenames to exclude section of the script.
I added three, as you can see. Those will be excluded for me.

Usage

While having any note open, activate the Templater script (with a hotkey, if you have bound one).
All filenames except for those excluded in the array are added to the clipboard in the following format:
E.g.:
file: /^(@AddamsB1995|Culinary Habits|Kitchen Etiquette)\.md/

  • Explanation of what’s here: this is a regular expression format query for files populated one after the other where the | basically means OR, meaning all of the items will be valid parent files to search in. We wrapped the items in brackets. The ^ and \.md parts had to be added to get the exact filenames and none other.

Paste what’s on the clipboard into the search modal bar and type in your query.
E.g.:
file: /^(@AddamsB1995|Culinary Habits|Kitchen Etiquette)\.md/ utensils

You can type in a regex search after the pasted bit:
file: /^(@AddamsB1995|Culinary Habits|Kitchen Etiquette)\.md/ /<regular expression here>/

  • For example: file: /^(@AddamsB1995|Culinary Habits|Kitchen Etiquette)\.md/ /cut.*?cucumber/
    • .*? means any characters between the word stem cut and cucumber, including zero characters
    • the ending / at the end of your query is optional

Hopefully, you’ll find what you’ve been looking for!

Plus

This can also be used for finding common themes among your Zotero-imported annotation files and any randomly opened or carefully selected files which already have some (rather long) content.

Possible upgrades

If anyone would like to build on it, they can add some user input bit for what would be the search term pasted to the end of what we’ve got now.

Then you can also activate the search modal pane so all the query will show up there without having to paste anything in.

See ver. 2 below with these upgrades.

A further upgrade would be reading the content of all open markdown files and find common themes based on some patterns.

1 Like

Well, in the meantime I was fiddling with some Meta Bind buttons which open links: performs searches…
Then I thought, while I’m at it, I am going to upgrade the snippet above:

Ver.2 of the script shared above

<%*
// Get the active workspace
const workspace = app.workspace;

// Get all open leaves (tabs)
const leaves = workspace.getLeavesOfType("markdown");

// Define the list of filenames to exclude
const excludeFilenames = [
    "Collections Query",
    "fifty latest notes",
    "quicksearch"
];

// Extract filenames, filter out excluded filenames
const filenames = leaves
    .map(leaf => leaf.view.file.basename)
    .filter(filename => !excludeFilenames.includes(filename));

// Format the filenames for search modal
let formatted = `file: /^(${filenames.join("|")})\\.md/`;

// URL-encode only the `file:` part of the query
const encodedFileQuery = encodeURIComponent(formatted);

// Use Templater's system prompt to get the main query from the user
const userQuery = await tp.system.prompt("Enter search term:");

// Append the user's search term to the file search query if provided
if (userQuery && userQuery.trim()) {
    formatted += ` ${userQuery.trim()}`;
}

// URL-encode the complete query
const encodedQuery = encodeURIComponent(formatted);

// Construct the Obsidian URI and open the search automatically
const uri = `obsidian://search?query=${encodedQuery}`;
const link = document.createElement('a');
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

// Copy the result and the URL-encoded `file:` query to the clipboard as an afterthought
setTimeout(() => {
    const combinedClipboardText = `${formatted}\n\nEncoded file query:\n${encodedFileQuery}`;
    navigator.clipboard.writeText(combinedClipboardText).then(() => {
        // Show a notice to the user
        new Notice(`Copied to clipboard:\n${formatted}\n\nEncoded file query:\n${encodedFileQuery}`);
    }).catch(err => {
        console.error('Could not copy text: ', err);
    });
}, 100);
_%>

When you execute the script, you are asked to enter your search term (utensil or /cut.*?cucumber/ from my examples above – so normal or regex with /syntax/ searches are both supported) and this will be appended to the file: query (which part is handled by the script).
Search in the dedicated core search modal pane will be automatically initiated so no need to paste anything in the search bar with this upgrade of the script.

Didn’t test (the DOM element click simulation method, preferred over window.location.href method) on mobile but it should work there too.

If you are reluctant to touch (edit) the script, you can leave the list of filenames to exclude as it is. It’s not going to make a difference. Script works as it is.

Again, for those who don’t know what’s going on:
Executing the script enables you to limit your search to content in markdown files that are currently open.
Faster than copying and pasting search term after clicking on each tab one by one…

Upgrade 2B

Use case 2 - copy url-encoded versions of files currently open

I kept ‘the add to clipboard’ part and the script even adds the URL-encoded versions of the files currently open. Which is handy if you want to re-use these for Obsidian URI or Advance URI functionality, for example for a Meta Bind button. An example of such button:

```meta-bind-button
style: primary
label: Translations
id: trigger-translations
action:
  type: open
  link: obsidian://search?&query=file%3A%20%2F%5E(Translations%20%E2%80%93%20same%20translations%20in%20multiple%20places)%5C.md%2F
```
  • You add the copied line after the obsidian://search?&query= part.
1 Like