When working with an MVC framework, my controllers and models have similar names which can sometimes cause confusion when I have a controller and model files open and they are similarly named.
I looked through many settings and couldn't find any option to rename the tabs or group them together. There used to be a package for it however it was for Sublime Text 2.
I understand that I can simply rename the files themselves but I want them to be as closely named to the controller as possible.
Also in case anyone asks, I am using a self-made framework that we use for in-house internal systems.
Is there a way to accomplish this, or a method to better organise the file tabs so I can have all my models, controller, view and other files in some sort of tab-group? Or perhaps there is a view/layout that would address this issue?
I found the old RenameTab plugin for Sublime Text 2 and made a few little changes.
Create RenameTab.py and save it in the Packages/User folder:
import sublime
import sublime_plugin
class RenameTabCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.window().show_input_panel("Tab Name:", self.view.name(), self.on_done, None, None)
def on_done(self, input):
self.view.set_name(input)
Create Tab Context.sublime-menu and save it in the Packages/User folder:
[
{ "command": "rename_tab", "caption": "Rename Tab" }
]
Add this to the Sublime Keybindings settings (re-map it to whatever key you want):
{ "keys": ["alt+w"], "command": "rename_tab", "context":
[
{ "key": "setting.is_widget", "operator": "equal", "operand": false }
]
}
Right-click on the file tab at the top and press "Rename Tab" in the context menu and it will prompt you to create a new name. This won't change the filename, just the name of the tab for easy viewing/reading/organising.
RenameTab was written by frozenice, however he hasn't changed it since 2012, or tested it for Sublime Text 3. I changed the layout of the import sublime and import sublime_plugin. (I'm not even sure if that made a difference.)
Related
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())
So I'm trying to create a plugin and execute it with the context menu. In my plugin, I want to make use of the mechanical soup module.
I have configured the Context.sublime-menu file as follows to create the Context menu option:
[
{
"id": "SlackSnippets",
"caption": "SlackSnippets",
"children":
[
{"id": "wrap"},
{ "command": "slacksnippet" }
]
}
]
Now, with this, the extreme basic functionality of the context menu works, it shows up when you right-click, and it's child command is clickable:
.
However, where I'm running into trouble is when I then try to import the mechanical soup module in the python file where the 'SlackSnippet' command is defined:
from __future__ import print_function
import os
import mechanicalsoup """If Commented out, I can click the command in the context window"""
import sublime
import sublime_plugin
class SlacksnippetCommand(sublime_plugin.TextCommand):
def run(self, edit):
## Code
The command in the Context menu then greys out and I am unable to use it:
I am new to creating plugins in Sublime Text 3 and any help with this would be greatly appreciated!
The reason that your command is grayed out in the second case is because Sublime can't load the plugin with the line you mention in place. Thus it doesn't know about the command the menu entry is telling it to execute, and so it is disabled (if you were using the command palette, the command would not appear at all in this case).
If you open the Sublime console with Ctrl+` or View > Show Console you'll see a stack trace saying this:
ImportError: No module named 'mechanicalsoup'
The first thing to note is that Sublime Text contains its own embedded version of Python 3.3.6 that is completely distinct from any version of Python you may have installed on your computer.
As such, it is unable to load system installed Python modules by default because it doesn't know anything about them. It's possible to make Sublime look at system locations, but it's generally not a good idea because it introduces a lot of external dependencies that users of your plugin/package will need to install before your code works (including installing Python 3.3.6 itself).
Generally speaking, the Python interpreter embedded in Sublime looks for Python files/modules in a few locations, but the only ones that are meant for users to interact with are:
The Sublime Text Packages folder, which you can get to by selecting Preferences > Browse Packages from the menu
A folder named Lib/python3.3 (Lib is stored in the same place as the Packages folder; use that command and go up one folder level)
You have the option of putting your module (e.g. mechanicalsoup in the Packages folder. However, this is not a particular good idea because Sublime will try to load the top level Python source files automatically (because it thinks it is a package) which is not what you want and may not actually work except in simple cases.
Sublime ignores any Python files that are not in the top level of a package folder unless you tell it explicitly to load it (i.e. via import). So you can put your modules directly inside of your package folder and import them from there.
For example, if your package was named SlackSnippets, your package folder might look like this:
SlackSnippets/
|-- mechanicalsoup
| |-- __init__.py
| |-- __version__.py
| |-- browser.py
| |-- form.py
| |-- stateful_browser.py
| `-- utils.py
`-- slack_snippet_cmd.py
Now you can import the module by specifying the module name SlackSnippets.mechanicalsoup:
from __future__ import print_function
import os
import SlackSnippets.mechanicalsoup
import sublime
import sublime_plugin
class SlacksnippetCommand(sublime_plugin.TextCommand):
def run(self, edit):
pass
(Note: This still won't work because this module requires other modules to be installed, such as requests. I'm not sure how deep that particular rabbit hole goes, however, since I don't use this module.)
Depending on the module in question and how it's written, there may be more work involved in getting things to work this way, such as if there are operating system specific libraries that are needed.
That leaves us with the second option mentioned above, which is to put your modules in the Lib/python3.3 folder so Sublime will be able to find them.
In this case since that folder is in the search path, your code as originally posted will load the module without any changes (although it will still fail to work here due to no requests module being available).
You (and your users) are still still responsible for putting the module(s) there, though; Sublime doesn't do that sort of thing for you on it's own. If you plan on using Package Control to distribute your package when you're finished, you can get a little respite from this.
Package Control supports the notion of dependency modules and with some set up will install modules for the user (if they're not already installed) when your package is installed.
Currently this is handled by Package Control by putting the module into the Packages folder with a special package layout followed by doing some work behind the scenes so that Sublime will see it as a regular module.
In order to get this to work you need to have the dependencies that you require also be stored in Package Control, which requires more set up work by you if the modules that you're interested in aren't already available.
Currently mechanicalsoup is not available, although requests is. The list of available dependencies is here.
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")
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"
},
// ...
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.