Skip to content

Instantly share code, notes, and snippets.

@OdatNurd
Created August 22, 2023 04:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OdatNurd/fd3cb2e03a323d7f0f2801f59bbda7e6 to your computer and use it in GitHub Desktop.
Save OdatNurd/fd3cb2e03a323d7f0f2801f59bbda7e6 to your computer and use it in GitHub Desktop.
Sublime Text 4 plugin that will cause reloaded log files to jump to the bottom of the file on new content
import sublime
import sublime_plugin
from fnmatch import fnmatch
# Related Reading:
# https://forum.sublimetext.com/t/auto-scroll-to-bottom-tail-log-files/69156
#
# For any opened file that matches one of the globs set out below, this will
# cause the view of the file to jump to the bottom whenever the file reloads
# and:
# - There is exactly 1 cursor
# - That cursor is on the last line of the file when the reload happens
# - The selection is empty
# - The plugin is enabled for the current file
# - The file is not read only (because Sublime will not tell us about reloads
# in that case)
# - You did not hand edit the file within sublime (because this will mess up
# the track of what the last line in the file is).
#
# The included 'toggle_log_tail' command can be used to enable or disable the
# plugin within the current file; the default is to be turned on.
# The list of file patterns that represent a log file; any loaded file that
# matches an entry in here will be considered a log. Partial paths are allowed,
# e.g. '*/coollogs/*.log' to only trigger for logs in a specific path.
LOG_GLOBS = ['*.log']
# When this setting is set to True in a view, the plugin will assume that that
# file is a log file.
IS_LOG = '_is_log_file'
# When this setting is set to True in a file where IS_LOG is also set to true,
# a non-read-only file that gets reloaded will scroll to the bottom of the
# new content IF the cursor was in the last line of the file.
TAIL_ENABLED = '_log_tail_enabled'
# The status key used to display the log tail status for a log file in the
# status bar, and the text used to display the status in the status bar for
# each of the two conditions.
TAIL_STATUS = '_log_tail_status'
_tail_status_keys = {
True: '[Log Tail: ENABLED]',
False: '[Log Tail: DISABLED]',
}
## ----------------------------------------------------------------------------
class ToggleLogTailCommand(sublime_plugin.TextCommand):
"""
Toggle the state of the plugin within a specific log file to enabled or
disable the jump to the bottom of the file when it gets reloaded. This will
only enable itself in files that have been marked as log files by the event
listener.
The status bar for this view is updated to show the new state.
"""
def run(self, edit):
enabled = not self.view.settings().get(TAIL_ENABLED, False)
self.view.settings().set(TAIL_ENABLED, enabled)
self.view.set_status(TAIL_STATUS, _tail_status_keys[enabled])
def is_enabled(self):
return self.view.settings().get(IS_LOG, False)
## ----------------------------------------------------------------------------
class CustomLogFileListener(sublime_plugin.ViewEventListener):
"""
A view event listener that applies only to files that appear to be logs;
this watches for reloads on the file, and will scroll the view to the
bottom of the window if the selection in the window is a single, empty
selection that is within the confines of the last line in the window.
This does not work for read-only views because Sublime will not raise an
on_reload event for views that are not mutable.
"""
_last_line = None
def __init__(self, view):
"""
When we get instantiated, capture the region that encompasses the last
line in the file; this happens here instead of in an on_load because
on_load won't trigger for files that are already open when the plugin
loads, and on_init() is not a ViewEventListener supported event.
"""
super().__init__(view)
self._last_line = view.line(len(view))
@classmethod
def is_applicable(cls, settings):
"""
This listener only applies to views which have the internal setting in
them flagging that they are log files.
"""
return settings.get(IS_LOG, False)
def should_do_scroll(self):
"""
Determine whether the current file should be scrolled to the end of
the file to reveal the content there.
This happens when:
There is a single, empty selection that is in the line that used
to be the last line prior to the reload happening.
The file is not read-only, since the reload event will not trigger
in that case.
The current state of the plugin is turned on for this view.
"""
if len(self.view.sel()) != 1 or len(self.view.sel()[0]) != 0:
return False
if self.view.settings().get(TAIL_ENABLED, False) == False:
return False
# We should only scroll if the cursor is inside of the last line.
return self._last_line.contains(self.view.sel()[0].b)
def on_reload(self):
"""
On reload of a log file, if the cursor is within what used to be the
last line of the file, scroll to the end.
"""
if self.should_do_scroll():
self.view.run_command("move_to", {"to": "eof"})
# Whether we scrolled or not, update the saved last line of the file
# for the next reload.
self._last_line = self.view.line(len(self.view))
## ----------------------------------------------------------------------------
class LogFileLoadEventListener(sublime_plugin.EventListener):
"""
This global event listener listens for files being loaded so that it can
check their filename to see if they're log files or not. If they are, it
sets the setting that causes the view event listener to know it should be
tracking the view.
"""
def on_load(self, view):
"""
Check to see if the loaded file is a log file; if so mark it as one
with the key setting that will enable the view event listener and run
the toggle command to update the status bar and turn on the plugin.
"""
for glob in LOG_GLOBS:
if fnmatch(view.file_name(), glob):
view.settings().set(IS_LOG, True)
view.run_command('toggle_log_tail')
return
## ----------------------------------------------------------------------------
@OdatNurd
Copy link
Author

This plugin is the result of someone asking a question about log tailing in the Sublime Text forum.

The plugin will watch for log files being opened and set a flag within them. When that file reloads, if there was only a single cursor within the file, and that cursor was an empty selection in the last line of the file, the view will jump to the end of the file on reload.

This lets you watch an externally changing log file without having to scroll manually, while still giving giving you the power to stop it from happening.

There is a toggle command that you can use to temporarily turn the plugin off in any individual file, should that be required.

This was coded live on my [https://twitch.tv/odatnurd](Twitch Channel).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment