How to find the tab of an open file by name in all Sublime Text windows
Solution 1:
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! :-)
Solution 2:
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) >= 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 >= 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"{type(e).__name__} at line {e.__traceback__.tb_lineno} of {__file__}: {e}")
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 > 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 > 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()) > 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 ""
if not view.file_name():
return '<unsaved>'
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