Test coverage reports for sublime syntax files
Mentioned by @FichteFoll here:
Mentioned by @FichteFoll here:
Add an EventListener callback that is run whenever a project changes or is opened.
Scale: Minor
class MyProjectChangeListener extends sublime_plugin.EventListener:
def on_project_changed(self, window, new_project_data):
""" Called when the current project data changes. """
...
sublime.open_project('/path/to/.sublime-project')
sublime.open_folder('/path/to/folder')
sublime.active_window().add_folder('/path/to/folder')
The above api removes boilerplate such as:
gerardroche/sublime-open-sesame
class Window():
def open_project_in_new(self, sublime_project_file):
"""
Open a project in a new window
"""
if not sublime_project_file:
return
if not os.path.isfile(sublime_project_file):
return
if not re.match('^.+\.sublime-project$', sublime_project_file):
return
sublime.set_timeout_async(lambda: subl(['--new-window', '--project', sublime_project_file]))
def open_folder_in_new(self, folder):
"""
Open a folder in a new window
"""
if not folder:
return
if not os.path.isdir(folder):
return
sublime.set_timeout_async(lambda: subl(['--new-window', folder]))
def add_folder_to_current(self, folder):
"""
Add a folder to the current window
"""
window = sublime.active_window()
if not window:
return
if not folder:
return
if not os.path.isdir(folder):
return
project_data = window.project_data() if window.project_data() else {'folders': []}
# normalise folder
# @todo folder should be normalised to be relative paths to project file
folder = os.path.normpath(folder)
project_file_name = window.project_file_name()
if project_file_name:
project_file_dir = os.path.dirname(project_file_name)
if project_file_dir == folder:
folder = '.'
# check if it already exists
for f in project_data['folders']:
if f['path'] and folder == f['path']:
return # already exists
folder_struct = {
'path': folder
}
if folder != '.':
folder_struct['follow_symlinks'] = True
project_data['folders'].append(folder_struct)
window.set_project_data(project_data)
def subl(args=[]):
# credit: randy3k/Project-Manager
executable_path = sublime.executable_path()
if sublime.platform() == 'osx':
app_path = executable_path[:executable_path.rfind('.app/') + 5]
executable_path = app_path + 'Contents/SharedSupport/bin/subl'
subprocess.Popen([executable_path] + args)
class KeymapCommand(Command):
def is_context(self):
return False
def run(self):
pass
*.sublime-keymap
{
"keys": ["tab"],
"command": "command_name"
}
class CommandName(KeymapCommand):
def is_context(self):
# ...
def run(self):
# ...
Removes plugin boilerplate, duplication and allows plugins to circumvents possible performance issues.
For example, one way to currently achieve something similar to this api, but impacts performace and introduces duplication and more complicated code, is to use an event listener.
class FinishCompletionContext(sublime_plugin.EventListener):
def on_query_context(self, view, key, operator, operand, match_all):
if key == 'finish_completion':
if is_finish_completion_context(view):
return True
return None
The above can then be used as keymap context.
{
"keys": [")"],
"command": "finish_completion",
"context": [
{ "key": "finish_completion", "operator": "equal", "operand": true, "match_all": true }
]
}
The event listener in this case only needs to be fired for the )
keymap.
The following api:
sublime.logger('{{PLUGIN_NAME}}').emergency(message)
sublime.logger('{{PLUGIN_NAME}}').alert(message)
sublime.logger('{{PLUGIN_NAME}}').critical(message)
sublime.logger('{{PLUGIN_NAME}}').error(message)
sublime.logger('{{PLUGIN_NAME}}').warning(message)
sublime.logger('{{PLUGIN_NAME}}').notice(message)
sublime.logger('{{PLUGIN_NAME}}').info(message)
sublime.logger('{{PLUGIN_NAME}}').debug(message)
sublime.logger('{{PLUGIN_NAME}}').log($level, message)
`{{PLUGIN_NAME}}` is optional. Default to global logger.
Logging is enabled/disabled as follows:
`sublime.active_window().active_view().settings().get('log_level')`
`sublime.active_window().active_view().settings().get('{{PLUGIN_NAME}}.log_level')`
Default log_level
is info
To disable logger set to null
.
Removes a lot of plugin boilerplate.
Typically I use the following boilerplate in a lot of plugins:
DEBUG_MODE=bool(os.getenv('SUBLIME_{{PLUGIN_NAME}}_DEBUG'))
if DEBUG_MODE:
def debug_message(message):
"""
Prints a debug level message.
"""
print('[{{PLUGIN_NAME}}] %s' % str(message))
else:
def debug_message(message):
pass
Vintageous writes its own plugin logger. One problem with that is that it puts log files in Packages/.log/*.log
. Ideally they would be put somewhere more appropriate.
2 posts were split to a new topic: API Suggestion Discussion: Builtin logging
Importance: minor
Description: More control over the status bar in the API (and UI).
Motivation: A recurring use case is the permanent display of information about the current view (or project), but it hard to find a specific status bar section sometimes. Additionally, some status bar info could use a feature to display more information or present the user with a list of options he can take (i.e. display a context menu)
Details:
It should be possible to:
.sublime-menu
file. (I’ll make a suggestion for dynamic menus next.)With the above, one could emulate all the current status-bar sections (on the right side) with just plugin code, except for syntax selection.
API:
view.set_status(...)
window.set_status(...)
sublime.set_status(key, text, on_click=None,
menu="Packages/Default/Indentation.sublime-menu")
# view, window and sublime define the section's visibility
#
# - key and text remain as they are currently
# - on_click: called when clicked
# - menu: resource path to a menu file that will be shown when right-clicked
On github: #15
Importance: minor
Description: Fill a menu with items that are generated on the fly by plugins, like the syntax or color scheme selection, or recent files/projects in the main menu.
Motivation: Many plugins would like to add items to a menu dynamically, which is the only thing you can’t do with menus right now.
Details:
In a .sublime-menu
file, you provide a { "source": "key" }
entry for a menu item. If a menu is to be displayed with an entry like this, ST would go through all EventListeners and call their on_query_menu
callback with the value of "source"
as the first parameter, similar to completions. Plugins would then be able to provide a list of menu items of the same structure as in .sublime-menu
files, including the ability to define more { "source": "key" }
items.
Alternatively, the { "command": "$key" }
syntax, as found in Main.sublime-menu
, could be used.
Example:
Main.sublime-menu
[
{
"id": "preferences",
"children": [
{
"caption": "Theme",
"children": [
{ "source": "my_menu" }
]
}
]
}
]
some.py
class MenuExpander(sublime_plugin.EventListener):
def on_query_menu(self, key):
if key != "my_menu":
return
theme_files = sublime.find_resources("*.sublime-theme")
theme_names = set(path.rpartition("/")[2] for path in theme_files)
return [{"command": "set_theme", "args": {"name": name}}
for name in sorted(theme_names)]
class SetThemeCommand(sublime_plugin.ApplicationCommand):
def description(self, name):
return name
def run(self, name):
prefs = sublime.load_settings("Preferences.sublime-settings")
prefs.set("theme", name)
sublime.save_settings("Preferences.sublime-settings")
Importance: minor
Description: Find out where a value in view.settings()
was defined. Could also be useful for any other settings object returned by sublime.load_settings(...)
.
Motivation: There are a lot of places where a setting can be defined (Preferences.sublime-settings (default, user, any other package…), project, syntax.sublime-settings (also multiple possible), view) and this is specifically important when you need to know whether you are overriding a view-specific setting and whether you should re-set its old value or erase your custom setting to not make the value stick.
It would also help with debugging.
API:
source = view.settings().get_source(key)
# returns a string that could be a resource path or `"project"` or `"view"`
Importance: trivial
Description: Several API hooks get called for all view types, including the console input and output, quick panels, input panels, find panels, goto anything … There is view.settings().get('is_widget')
, but it’s not documented and not a nice API.
Motivation: A way to distiguish these panels would be nice for fine-grained features such as providing completions and a properly documented API would certainly help with discoverability.
See also this issue for API ideas:
A post was split to a new topic: API Suggestion Discussion: EventListener for Project Changes
Please also fix this very annoying settings bug after cloning a view:
Priority for me: major
2 posts were merged into an existing topic: On after modified event
Importance: Major
Another set of bugs that affect the existing API are:
I currently have an auto-completion plugin that’s about 80% done, which will allow users and developers to easily define context-sensitive completions via YAML files. The macro-contexts are scope
& location
, and there is a micro-context that parses the current line’s text to allow nested completions.
Here is an example of a .custom-completions
file that would add Sublime Text API completions only to python
files contained within /Packages/
. It works great, with the exception that all buffer completions are overridden as mentioned in the issues above ( if certain snippet-particular characters are used ).
#▓▓▓▓▓║ Settings ║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
completionTrigger: "."
trailing_WhiteSpace_Enabled: false
scopeSelectors: [ "source.python" ]
locationContext_Mode: "Paths_StartWith"
locationContext_RegEx_Enabled: true
locationContexts: [ "(.*)(\\\\Sublime Text 3\\\\Packages\\\\)" ]
#▓▓▓▓▓║ completionSets ║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
completionSets:
view:
sel: "sel()"
line( point ): "line( ${1:__INTEGER__Point__} )"
line( region ): "line( ${1:__REGION__} )"
full_line( point ): "full_line( ${1:__INTEGER__Point__} )"
full_line( region ): "full_line( ${1:__REGION__} )"
lines: "lines( ${1:__REGION__} )"
split_by_newlines: "split_by_newlines( ${1:__REGION__} )"
word( point ): "word( ${1:__INTEGER__Point__} )"
word( region ): "word( ${1:__REGION__} )"
window:
id: "id()"
new_file: "new_file()"
open_file: "open_file( ${1:__STRING__File__}, ${2:»__FLAGS__} )"
find_open_file: "find_open_file( ${1:__STRING__File__} )"
active_view: "active_view()"
active_view_in_group: "active_view_in_group( ${1:__INTEGER__GroupNumber__} )"
views: "views()"
views_in_group: "views_in_group( ${1:__INTEGER__GroupNumber__} )"
num_groups: "num_groups()"
active_group: "active_group()"
focus_group: "focus_group( ${1:__INTEGER__GroupNumber__} )"
focus_view: "focus_view( ${1:__VIEW__} )"
I would also like the option of hiding some of the statuses that are managed by sublime or even other packages.
Sometimes the default information from sublime is redundant to something your own plugin needs to display, in my case, an emacs-like incremental search package. If I could enumerate the existing status keys and hide them, perhaps for the duration of an input panel’s visibility or something like that.
I would like to see an API to initialize settings in the find/replace forms.
eg.:
window.find.setRegex(true)
window.find.setFind("([0-9]{2}),([0-9]{2}),([0-9]{4})")
window.find.setReplace("\2.\1.\3")
window.find_in_files.add_directory( myParentDirectory() )
window.find_in_files.exclude_directory("./OLDFILES/./")
so that I could write macros/plugins to set up commonly used searches and then use normal find next/find prev/find all commands to process them with existing commands.
or maybe a way to set up search/replace snippets collection.
No the goal is not to completely define a command’s key mapping within a plugin. The goal is remove plugin boilerplate, complexity, duplication, and performance footprint. I’ve update the post with extra details about motivation. I’ve removed the suggestion about defining the keys within the command itself, because I agree the keys should still be defined in the keymaps file.
on_close
event listeners should be able to abort the close actionImportance: Medium
Use case: a plugin may be managing a view with special functionality.
The view may even be set as scratch but contain information edited by the user.
The plugin should be able to detect the on_close
event of such view and ask the user what to do with any un-committed changes.
A dialog can be displayed but a “Cancel” option cannot be currently cleanly implemented.
(Think of a thin client of some web API: edits may need to be uploaded to be saved)