Sublime Text 3: confirm to delete file - sublimetext3

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.

Related

How to use system default icons for a file type in a QTreeView

for reference this is all using Pyqt5 and Python 3.6:
I've got a QStandardItemModel that is built from QStandardItems that are strings of the items in a zip (the model displays all the contents of a zipfile). I went with this choice as I can not cache the files locally, and my research shows that QFileSystemModel can not work on archives unless I unpack at least temporarily.
All items in the QStandardItemModel end in the correct extension for the file (.csv,.txt,ect), and I need to display the icon a user would see if they were looking at the file in windows explorer, however show it in the qtreeview (a user seeing content.csv should also see the icon for excel). On that note, this application is only running on windows.
How can I pull the extensions default system file icon, and set it during my setting of these items? Would I have to manually download the icons for my known file types and do this, or does the system store it somewhere I can access?
Here's some basic code of how I build and display the model and treeview:
self.zip_model = QtGui.QStandardItemModel()
# My Computer directory explorer
self.tree_zip = QTreeView()
self.tree_zip.setModel(self.zip_model)
def build_zip_model(self,current_directory):
self.zip_model.clear()
with zipfile.ZipFile(current_directory) as zip_file:
for item in zip_file.namelist():
model_item = QtGui.QStandardItem(item)
self.zip_model.appendRow(model_item)
You can use QFileIconProvider:
def build_zip_model(self, current_directory):
iconProvider = QtWidgets.QFileIconProvider()
self.zip_model.clear()
with zipfile.ZipFile(current_directory) as zip_file:
for item in zip_file.namelist():
icon = iconProvider.icon(QtCore.QFileInfo(item))
model_item = QtGui.QStandardItem(icon, item)
self.zip_model.appendRow(model_item)

I need to copy files from appdata/local to C drive and overwrite them each time the program is run

I use a program which, sadly corrupts some saved files at random times. To be helpful (although I am a novice at this) I am trying to make a Python program to basically backup those file from the AppData/local directory and put them in a folder on C. I need this program to overwrite the previously copied files each time it is run.
I need to generalize the AppData/local because each person who uses this program would, in theory, have a different user directory preceding the AppData folder.
I've tried running some of my own attempts at a solution.
I will post the results.
# Imports
import shutil
import os
import distutils
from distutils import dir_util
# Paths
# os.makedirs("C:/RevSaves-Backup")
path = '%LOCALAPPDATA%/Remnant'
backup_path = "C:/RevSaves-Backup"
# Procedures
print("The Very Basic Remnant Save Backup Utility")
print(" ")
print("Backing up the save source:")
print(path)
print(" ")
print("It is recommended you run this at regular intervals \nto ensure you have the latest saves up to date.")
distutils.dir_util.copy_tree(path, backup_path)
print("Backup completed.")
When I execute this via command prompt or PowerShell, I get the following message:
Traceback (most recent call last):
File "RevSaveBkUp.py", line 28, in
distutils.dir_util.copy_tree(path, backup_path)
File "C:\Users\candr\AppData\Local\Programs\Python\Python37-32\lib\distutils\dir_util.py", line 124, in copy_tree
"cannot copy tree '%s': not a directory" % src)
distutils.errors.DistutilsFileError: cannot copy tree '%LOCALAPPDATA%/Remnant': not a directory
I am having trouble "targeting" the system-specific local AppData folder.
After a lot of reading, I made the following solution if anyone else is trying to do something similar. I do not know if this is the "best" or "right" way of doing things, however.
Here is how I targeted the AppData Local folder regardless of the user logged in:
path = os.path.join(os.path.expanduser('~'), 'AppData', 'Local')
Some explanations for anyone who is new like me:
os.path.join basically connects folders together in the path. For example, using the above code, join would "connect" AppData to Local and the "User Folder" (referenced in the code as '~'). The output would look like this: C:\Users\your_username\AppData\Local
os.path.expanduser defines the user in question. For example, "~" targets the current user logged in. It goes inside the () because this is how you tell "your code" who, to target. If you wanted a specific user (if you had more than one) you could possibly use os.path.expanduser('Jane') I believe.
Keeping the notes above in reference, this method allowed me to define the variables I needed to and use them for the copy above, where I could not normally use the AppData directory as I wanted.
This was done by using the following code as an example:
path = os.path.join(os.path.expanduser('~'), 'AppData', 'Local')
backup_path = "C:/MyBackupFolder"
Finally we executed the copy with this:
distutils.dir_util.copy_tree(path, backup_path)
The above copied The AppData information I needed to the backup folder.
I hope this helps everyone learn as I did, it came in quite handy.

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"
},
// ...

Resources