Save file before running custom command in Sublime3 - sublimetext3

This question is similar to this one Is it possible to chain key binding commands in sublime text 2? Some years have passed since that question (and the answers given), and I'm using Sublime Text 3 (not 2), so I believe this new question is justified.
I've setup a custom keybind:
{
"keys": ["f5"],
"command": "project_venv_repl"
}
to run the project_venv_repl.py script:
import sublime_plugin
class ProjectVenvReplCommand(sublime_plugin.TextCommand):
"""
Starts a SublimeREPL, attempting to use project's specified
python interpreter.
Instructions to make this file work taken from:
https://stackoverflow.com/a/25002696/1391441
"""
def run(self, edit, open_file='$file'):
"""Called on project_venv_repl command"""
cmd_list = [self.get_project_interpreter(), '-i', '-u']
if open_file:
cmd_list.append(open_file)
self.repl_open(cmd_list=cmd_list)
def get_project_interpreter(self):
"""Return the project's specified python interpreter, if any"""
settings = self.view.settings()
return settings.get('python_interpreter', '/usr/bin/python')
def repl_open(self, cmd_list):
"""Open a SublimeREPL using provided commands"""
self.view.window().run_command(
'repl_open', {
'encoding': 'utf8',
'type': 'subprocess',
'cmd': cmd_list,
'cwd': '$file_path',
'syntax': 'Packages/Python/Python.tmLanguage'
}
)
This runs the opened file in a SublimeREPL when the f5 key is pressed.
What I need is a way to mimic the "Build" shortcut (Ctrl+B). This is: when the f5 key is pressed, the current (opened) file should be saved before running the project_venv_repl command.
Can this instruction be added to the project_venv_repl.py script, or to the command line in the keybind definition?

There's no need to do anything fancy. If you just want to save the current view before running the REPL, edit your ProjectVenvReplCommand class's run() method (which is called when the project_venv_repl command is executed) and add the following line at the beginning:
self.view.run_command("save")
This will silently save the current view unless it has not been saved before, in which case a Save As... dialog will open just like usual.
If you want to save all open files in the window, you can use this code:
for open_view in self.view.window().views():
open_view.run_command("save")

Related

searching for tags in Tkinter Text widget

i am using a tkinter Text widget to display the content of gerber-code files.
the program runs on a raspberry pi and send code over serial to a machine one line of text at at time.
i set the current active line as follows:
class TextEditor(tkinter.Text):
def __init__(self, tkRoot):
...
self.tag_configure("activeLine", background="#87e8ed")# set the colour used for activeLine
def setLine(self, lineNumber):
self.tag_remove("activeLine", "1.0", "end")
self.tag_add("activeLine", str(lineNumber)+".0 linestart", str(lineNumber)+".0 lineend+1c")
def getLine(self):
pass # need to return the activeLine line number
there should only ever be one line at a time highlighted with "activeLine" so the first instance would be fine.
i could store a variable in the call to setLine and read it back in getLine but i would prefer not to as any edits to the text it could go out of sink
i notice using IDLE that the debugger uses what looks the same principle as i am trying to achieve here to set breakpoints, is it possible and if so where would i start looking for the IDLE source code to look into how it is achieved there, i am currently writing this on a Ubuntu 18.04 desktop i would like to no best ways to search IDLE source from
any help would be greatly appreciated, i am quite new to python and tkinter as i am generally a windows dot.net programmer but i am now learning to use Linux
i have now found an answer to my own question
listing all the functions of the text widget that start with "tag_" like this:
d = dir(self.tkRoot.text)
for dv in d:
s = str(dv)
if s.startswith("tag_"):
print(dv)
i found the method "tag_ranges(name)" that returns me this
(<textindex object: '5.0'>, <textindex object: '6.0'>)
at the time of calling the current line was 5

closing a tab > go to the previous edited one

When closing a tab in sublimetext3, it always brings me back to the left one, whereas on sublimetext2, I was brought to the previously opened-one(not necessarily the left one).
That behavior was really handy in sublimetext2, because it created a kind of history that was easy to go-back through, just by closing tabs successively.
Is there a setting for that in sublimetext3 ?
Steps to reproduce
I have 3 tabs open, and the 3rd-one is active:
I now go and edit the 2nd-one:
I'm done and decide to close the 2nd-tab:
FAIL: i'm not back to the previously edited one: the 3rd-one
There is no setting to do that. However since it can be done with a plugin, and I like to keep my plugin writing skills up to scratch, I've written it for you.
It's called FocusMostRecentTabCloser and the code is both below and in this GitHub Gist.
To use it save it as FocusMostRecentTabCloser.py somewhere in your Packages directory structure and assign keys to the focus_most_recent_tab_closer command. e.g.
{"keys": ["ctrl+k", "ctrl+w"], "command": "focus_most_recent_tab_closer"},
I have not tested it extensively and it requires Sublime Text 3 build 3024 or later (but that's quite old now).
Reply with a comment if there are any bugs and I'll see what I can do.
# MIT License
import sublime
import sublime_plugin
import time
LAST_FOCUS_TIME_KEY = "focus_most_recent_tab_closer_last_focused_time"
class FocusMostRecentTabCloserCommand(sublime_plugin.TextCommand):
""" Closes the focused view and focuses the next most recent. """
def run(self, edit):
most_recent = []
target_view = None
window = self.view.window()
if not window.views():
return
for view in window.views():
if view.settings().get(LAST_FOCUS_TIME_KEY):
most_recent.append(view)
most_recent.sort(key=lambda x: x.settings().get(LAST_FOCUS_TIME_KEY))
most_recent.reverse()
# Target the most recent but one view - the most recent view
# is the one that is currently focused and about to be closed.
if len(most_recent) > 1:
target_view = most_recent[1]
# Switch focus to the target view, this must be done before
# close() is called or close() will shift focus to the left
# automatically and that buffer will be activated and muck
# up the most recently focused times.
if target_view:
window.focus_view(target_view)
self.view.close()
# If closing a view which requires a save prompt, the close()
# call above will automatically focus the view which requires
# the save prompt. The code below makes sure that the correct
# view gets focused after the save prompt closes.
if target_view and window.active_view().id() != target_view.id():
window.focus_view(target_view)
class FocusMostRecentTabCloserListener(sublime_plugin.EventListener):
def on_activated(self, view):
""" Stores the time the view is focused in the view's settings. """
view.settings().set(LAST_FOCUS_TIME_KEY, time.time())

Plugin to open file at eof

I'm tryng to create a plugin that opens a .log file associated with a file i'm editing. I was able to open the file but could not make the cursor move to end of file, unless I run the code again when the file is already open.
import sublime
import sublime_plugin
class OpenlogCommand(sublime_plugin.TextCommand):
def run(self, edit):
if os.path.isfile(self.view.file_name()[:-3]+"log"):
a=sublime.active_window().open_file(self.view.file_name()[:-3]+"log")
a.run_command("move_to", {"to": "eof"})
Does anybody know how to do it?
The reason that this doesn't work unless the file is already open is because the loading of a file is asynchronous; the command to open the file returns right away and the file is loaded in the background if it's not already open.
Thus the first time you run the command, the move_to command does nothing because it's already at the end of an empty buffer, but when the file has already been loaded it does what you expect.
To get around that, you need to detect if the file is still loading and defer the call to jump to the end of the file until after it's finished. An example of that is the following:
import sublime
import sublime_plugin
import os
class OpenLogCommand(sublime_plugin.TextCommand):
def run(self, edit):
log_name = self.view.file_name()[:-3] + "log"
log_view = self.view.window().open_file(log_name)
if log_view.is_loading():
log_view.settings().set("_goto_eol", True)
else:
log_view.run_command("move_to", {"to": "eof"})
def is_enabled(self):
fname = self.view.file_name()
if fname is not None and not fname.endswith(".log"):
return os.path.isfile(fname[:-3] + "log")
return False
class OpenLogListener(sublime_plugin.EventListener):
def on_load(self, view):
if view.settings().get("_goto_eol", False):
view.settings().erase("_goto_eol")
view.run_command("move_to", {"to": "eof"})
An issue with your existing version of this is that the file_name() method returns None if the file has not been saved to disk yet. Thus if you run that command on an unsaved file it will generate an error in the console. This is harmless, but a little unclean since it might be a red herring if you have other problems and happen to see those errors in the console.
Here the command will only enable itself if the file has been saved to stop that kind of problem. It will also only enable itself if it's not already a log file (since that would be redundant), and where an associated log file actually exists.
When a command is disabled, you can't execute it. That means that it also won't show up in the Command Palette and it will appear grayed out in menus (assuming you've added it to either of those).
When you run the command, it first calls open_file to open the associated log file, and then asks the view "Are you still loading?". When the view says NO, it means that the file is already open, and so we can immediately jump to the end of the file.
If the view says YES to this question, we then set a temporary setting in the view so that we know that when the contents of this view is finished loading, we want to jump to the end of the buffer.
The event listener asks every view as it's finished loading if it has this setting set, and when it does it will remove the setting and then jump to the end of the file.
[edit]
As mentioned in the comments below, the move_to command behaves slightly differently for a file that's already open versus a file that has just finished loading.
I'm not entirely sure why that's the case, but I suspect that there is some subtle interplay between the on_load notification being delivered right when the file content has been loaded but not yet displayed or something along those lines, although this is just a guess.
In any case, the most expedient fix would be to make a slight modification to the event listener by replacing that part of the code above with this instead:
class OpenLogListener(sublime_plugin.EventListener):
def on_load(self, view):
if view.settings().get("_goto_eol", False):
view.settings().erase("_goto_eol")
sublime.set_timeout(lambda: view.run_command("move_to", {"to": "eof"}), 1)
This changes things up a bit so that the call to the move_to command effectively happens after all event handling has been completed. That seems to resolve the issue on my test machine, at least.

Ship default sublime-settings with package

In my SublimeText package, I include a file BlameHighlight.sublime-settings. During testing, I link from ~/Library/Application\ Support/Sublime\ Text\ 3/Packages to this directory, and the changes to this file takes effect.
I also include a Menu item that points to ${packages}/User/BlameHighlight.sublime-settings. When I use the menu, I see a completely blank file.
How can I use my version of BlameHighlight.sublime-settings as the default template for ${packages}/User/BlameHighlight.sublime-settings?
Typically, your Main.sublime-menu will contain entries both for your default .sublime-settings file (probably Packages/BlameHighlight/BlameHighlight.sublime-settings) as well as the Packages/User version. If the user wants to customize the settings, they open the default file first (which is read-only in ST3) then the user one, and copy-paste what they need from default to user.
If for some reason you want the user file to be pre-populated with some settings, you'll need to programatically create it.
I would recommend to use the same strategy as every other package and not automatically create a copy of the default settings file. Not because I think its better, but because I think the user experience should not differ between the packages.
However as MattDMo stated you have to write our own plugin for this. At least for ST3 this is pretty straight forward:
import os, sublime_plugin, sublime
class CopyUserSettingsCommand(sublime_plugin.WindowCommand):
def run(self, package_name, settings_name):
file_path = os.path.join(sublime.packages_path(), "User", settings_name)
if not os.path.exists(file_path):
try:
content = sublime.load_resource("Packages/{0}/{1}".format(package_name, settings_name))
with open(file_path, "w") as f:
f.write(content)
except:
print("Error copying default settings.")
self.window.open_file(file_path)
Just copy this into a python file in your package and insert in the menu entry:
// ...
{
"command": "copy_user_settings",
"args": {
"package_name": "BlameHighlight",
"settings_name": "BlameHighlight.sublime-settings"
},
"caption": "Settings – User"
},
// ...

Sublime Text 3: confirm to delete file

Is there a way to confirm deleting a file from the tree (left hand side) or remove the option from the context menu?
It is too easy to miss i.e. rename and click delete file instead. Then the file is gone.
I googled and found it should be moved to the trash folder but either that doesn't apply to Win7 or to using network drives. As a result the files are actually deleted or moved somewhere I have failed to track them down so far.
Using Sublime Text (build 3083)
Important: take a look at iron77 answer. It says that if you modify Default.sublime-package (options 1 and 3) this changes might be overriden if sublime text is updated.
Option 1: modify side_bar.py file
You can use sublime API to show an ok/cancel dialog. The code you are looking for is in a file called side_bar.py. This file is located inside the zip file Default.sublime-package. In windows this is usually located in C:\Program Files\Sublime Text 3\Packages\Default.sublime-package and can be explored using programs such as WinRar.
Inside that file locate DeleteFileCommand and add this 3 new lines, so it is changed from this:
class DeleteFileCommand(sublime_plugin.WindowCommand):
def run(self, files):
# Import send2trash on demand, to avoid initialising ctypes for as long as possible
import Default.send2trash as send2trash
To this
class DeleteFileCommand(sublime_plugin.WindowCommand):
def run(self, files):
isSure = sublime.ok_cancel_dialog('Are you sure you want to delete the file?')
if isSure != True:
return
# Import send2trash on demand, to avoid initialising ctypes for as long as possible
import Default.send2trash as send2trash
We are showing a ok/cancel dialog and if the user doesn't press Ok then we return and the file isn't removed.
Notes:
You will have to add the same code in class DeleteFolderCommand in order to confirm also when deleting folders.
Is a good practice to backup your Default.sublime-package file first just in case something goes wrong. EDIT: use a different folder for the backup or the package could be loaded twice causing problems as the OP has said in his comment.
As this is python code indentation is extremly important, don't
replace any spaces for tabs nor add any extra space or it will not
work (you can see it console).
Result:
Option 2: use an existing package
As user leesei said in his answer you can use SideBarEnhancements package to achieve your goal. This package adds many other features to the file context menu as you can see in the following image, but it is a very good choice as you only need to install an exsiting package.
Option 3: remove option from context menu
Edit Side Bar.sublime-menu inside Default.sublime-package (see option 1) and remove this line (and if you want remove also the line reffering to deleting folders):
{ "caption": "Delete File", "command": "delete_file", "args": {"files": []} },
While sergioFC's answers work great, I'm bit worried of modifying Default.sublime-package, as it might someday get overwritten when Sublime is updated, so the fix would need to be manually re-applied after each such update. SideBarEnhancements, on the other hand, might have too many features for someone who only wants the confirmation when deleting a file.
Alternatively, you can add a simple confirmation dialog that should be more resistant to ST updates, by creating a file (plugin). On Linux it should be somewhere around ~/.config/sublime-text-3/Packages/User/confirm_delete.py, and if you're on Windows/Mac or this path does not work for you, you can simply choose from the top menu: Tools -> Developer -> New Plugin and later save as confirm_delete.py - thanks to harrrrrrry for this suggestion. Code to put in:
from Default.side_bar import *
class DeleteFileCommand(sublime_plugin.WindowCommand):
def run(self, files):
if len(files) == 1:
message = "Delete File %s?" % files[0]
else:
message = "Delete %d Files?" % len(files)
if sublime.ok_cancel_dialog(message, "Delete") != True:
return
# Import send2trash on demand, to avoid initialising ctypes for as long as possible
import Default.send2trash as send2trash
for f in files:
v = self.window.find_open_file(f)
if v != None and not v.close():
return
send2trash.send2trash(f)
def is_visible(self, files):
return len(files) > 0
This code is basically a copy of DeleteFileCommand function from Default.sublime-package's side_bar.py combined with confirmation dialogs from DeleteFolderCommand from the same file, as Sublime has such dialog natively for folder removal.
When I choose delete by right clicking on a file in the SideBar, I get a confirmation.
Maybe it's SideBarEnhancements. It is worth a try.
WTF a software that doesn't have a confirm dialog before delete. I can't believe this. Sad but true. Just stupid software!
Unfortunately there is no way to activate a confirmation. Usually the the deleted file is moved to the trash folder but as you mentioned this is only true for local files. Files on a shared network drive are still deleted immediately. This is a Windows 'feature' :(
Locally the Recycle Bin is part of Windows Explorer -- and on the network you are NOT dealing with explorer on the server. Explorer locally isn't going to copy the file to the user's workstation just to put it into the recycle bin.
You CAN implement Microsofts Shadow Copy however, then users can undelete and compare versions. This would be the only way so far for network drives until the sublime developer decides to make an optional confirmation dialog.
According to #iron77's answer, the path for plugin could not exist (in my case). An easier way is:
1) Click Sublime Text topbar menu Tools -> Developer -> New Plugin.
2) Paste the snippet
from Default.side_bar import *
class DeleteFileCommand(sublime_plugin.WindowCommand):
def run(self, files):
if len(files) == 1:
message = "Delete File %s?" % files[0]
else:
message = "Delete %d Files?" % len(files)
if sublime.ok_cancel_dialog(message, "Delete") != True:
return
# Import send2trash on demand, to avoid initialising ctypes for as long as possible
import Default.send2trash as send2trash
for f in files:
v = self.window.find_open_file(f)
if v != None and not v.close():
return
send2trash.send2trash(f)
def is_visible(self, files):
return len(files) > 0
3) Save as confirm_delete.py.

Resources