1

Answers like https://stackoverflow.com/questions/20519040/search-in-all-files-in-a-project-in-sublime-text-3 and Search all within opened tabs with Sublime Text 2 tell how to search open files by their contents, using ++F and Where: <open files>.

But when I try this, it only searches files open in tabs in the current window. If I have two windows open with 10 tabs each, and I use ++F in one of the windows, the search results tell me that it searched 10 files, not 20.

Now if I have a dozen windows open, I don't want to have to cycle through each one searching for the file I want. (Yes I should clean up my clutter, but that's not the question I'm asking.)

Is there a way to find an open file in any open window?

I'd also like to find the file by name (or tab title, which is usually the same) rather than by contents. But I'd settle for searching by contents if I could search across all open windows.

Update: Things that seem like they ought to work

I've found packages like Emacs Pro Essentials and ExtendedTabSwitcher, both of which describe features that allow you to switch to other views/buffers/tabs by name. Both packages explicitly say that the default behavior is to do this across all groups (which I believe means across all windows). Yet when I try them, only tabs from the same window show up in the list to choose from.

The built-in Goto > Goto Anything menu item behaves the same way (unanswered ten-year-old technical support forum request).

It makes me wonder if there's something specific to the Mac version that is preventing all these tools from accessing tabs in other windows.

LarsH
  • 984

2 Answers2

2

You could solve this with a small package. Here's my try: create a folder named "winfinder" unter the Sublime 3 package folder (on the Mac, this would be ~/Library/Application Support/Sublime Text 3/Packages/winfinder).

Next, create a file main.py in that folder with this content:

import sublime
import sublime_plugin

class WinFindCommand(sublime_plugin.TextCommand):

    def search(self, search_string):
        l = []
        for w in sublime.windows():
            for sh in w.sheets():
                fn = sh.view().file_name()
                if fn is not None:
                    if search_string.lower() in fn:
                        l.append(fn + "\n")
        if len(l) > 0:
            v = sublime.active_window().new_file()
            v.set_name("SearchResults")
            v.run_command("insert",{"characters": str(len(l)) + " matches:\n\n"})
            v.run_command("insert",{"characters": "\n".join(l)})
        else:
            sublime.message_dialog("No match found.")


    def run(self, edit):
        w = sublime.active_window()
        w.show_input_panel("Search text", "", self.search, None, None)

Now we need a way to invoke the functionality. This is done by creating a file named main.sublime-commands in the same folder. Content is as follows:

[
    { "caption": "WindowFind: find in window title", "command": "win_find" },
]

Usage

If you open the command palette and type "WindowFind", you should see the command. Press [ENTER] and the package will prompt you for a search string to be searched for in all tabs of all windows. If there is no match, a message is displayed.

If there is a match, a new tab names "SearchResults" will be opened with the search results:

3 matches:

/Users/your_user/notes/daylog.txt

/Users/your_user/Documents/2018/paychecks.csv

/Users/your_user/source/python/daily_tweets/daily.py

(search string was "ay") -- just testet it on Sublime 3, works. Thanks for the idea, this is helpful! :-)

Arminius
  • 131
1

Using an idea from @Arminius' answer, I tweaked the "Switch to View" code from the Emacs Pro Essentials plugin, so that it now works with views across all windows. Here is the resulting code in main.py:

# coding=utf-8
# Sublime plugin to search open files by filename, across all windows.
# See problem statement at https://superuser.com/questions/1327172/how-to-find-the-tab-of-an-open-file-by-name-in-all-sublime-text-windows

Code adapted from https://github.com/sublime-emacs/sublemacspro/blob/master/switch_to_view.py

and https://superuser.com/a/1328545/75777

import sublime, sublime_plugin, os

Switch buffer command. "C-x b" equiv in emacs. This limits the set of files in a chooser to the

ones currently loaded. Do we sort the files by last access? like emacs

class SwitchToViewCommand(sublime_plugin.TextCommand): def run(self, util, current_group_only=False, preview=False, completion_components=2, display_components=1): self.preview = preview self.completion_components = completion_components self.display_components = display_components window = self.window = sublime.active_window() self.group = window.active_group() # was: self.views = ViewState.sorted_views(window, window.active_group() if current_group_only else None) self.views = [sh.view() for w in sublime.windows() for sh in w.sheets()] ## TODO: sort the above views? # if window.num_groups() > 1 and not current_group_only: # self.group_views = set(view.id() for view in sorted_views(window, window.active_group())) # else: # self.group_views = None self.roots = get_project_roots() self.original_view = window.active_view() self.highlight_count = 0

    # swap the top two views to enable switching back and forth like emacs
    if len(self.views) &gt;= 2:
        index = 1
    else:
        index = 0
    window.show_quick_panel(self.get_items(), self.on_select, 0, index, self.on_highlight)

def on_select(self, index):
    if index &gt;= 0:
        # Does this work even on views that aren't in self.window?
        v = self.views[index]
        if v.window() is not self.window:
            try:
                v.window().bring_to_front()
                v.window().focus_window()
                # Strangely, I get the error 'Window' object has no attribute 'focus_window'
                # even though the documentation says it should be there in this version of ST.
            except AttributeError as e:
                print(f&quot;{type(e).__name__} at line {e.__traceback__.tb_lineno} of {__file__}: {e}&quot;)
        v.window().focus_view(self.views[index])
    else:
        self.window.focus_view(self.original_view)

def on_highlight(self, index):
    if not self.preview:
        return
    self.highlight_count += 1
    if self.highlight_count &gt; 1:
        # if self.group_views is None or self.views[index].id() in self.group_views:
        self.window.focus_view(self.views[index])

def get_items(self):
    if self.display_components &gt; 0:
        return [[self.get_path(view), self.get_display_name(view)] for view in self.views]
    return [[self.get_path(view)] for view in self.views]

def get_display_name(self, view):
    mod_star = '*' if view.is_dirty() else ''

    if view.is_scratch() or not view.file_name():
        disp_name = view.name() if len(view.name()) &gt; 0 else 'untitled'
    else:
        disp_name = get_relative_path(self.roots, view.file_name(), self.display_components)

    return '%s%s' % (disp_name, mod_star)

def get_path(self, view):
    if view.is_scratch():
        return view.name() or &quot;&quot;

    if not view.file_name():
        return '&lt;unsaved&gt;'

    return get_relative_path(self.roots, view.file_name(), self.completion_components)

From https://github.com/sublime-emacs/sublemacspro/blob/master/lib/misc.py

Returns the relative path for the specified file name. The roots are supplied by the

get_project_roots function, which sorts them appropriately for this function.

def get_relative_path(roots, file_name, n_components=2): if file_name is not None: if roots is not None: for root in roots: if file_name.startswith(root): file_name = file_name[len(root) + 1:] break # show (no more than the) last 2 components of the matching path name return os.path.sep.join(file_name.split(os.path.sep)[-n_components:]) else: return "<no file>"

Get the current set of project roots, sorted from longest to shortest. They are suitable for

passing to the get_relative_path function to produce the best relative path for a view file name.

def get_project_roots(): window = sublime.active_window() if window.project_file_name() is None: roots = None else: project_dir = os.path.dirname(window.project_file_name()) roots = sorted([os.path.normpath(os.path.join(project_dir, folder)) for folder in window.folders()], key=lambda name: len(name), reverse=True) return roots

# From ViewState. But I'm not using these right now. Don't really want to build the data structure of ViewStates.

# Returns a list of views from a given window sorted by most recently accessed/touched. If group

# is specified, uses only views in that group.

# @classmethod

def sorted_views(window, group=None):

views = window.views_in_group(group) if group is not None else window.views()

states = [find_or_create(view) for view in views]

sorted_states = sorted(states, key=lambda state: state.touched, reverse=True)

return [state.view for state in sorted_states]

# Finds or creates the state for the given view. This doesn't imply a touch().

# @classmethod

def find_or_create(cls, view):

state = cls.view_state_dict.get(view.id(), None)

if state is None:

state = ViewState(view)

return state

LarsH
  • 984