Sublime Forum

API Suggestions

#55

Placeholder for fold

Importance: Minor
Description: Add an optional placeholder argument to the fold function, i.e. change the signature to fold(regions, <placeholder>) and fold([regions], <placeholder>). If the function is executed the placeholder text is inside the yellow rectangle instead of the 3 dots.
In addition add a function is_folded(point), which returns whether a point is currently folded.
Motivation: This could be used to fold redundant and unnecessary code away to improve readability while keeping the key information. E.g. fold all function keywords in JavaScript to f or python lambda: to Ξ». In LaTeX it could be used to fold labels to l and greek characters and some operators to their unicode character, e.g. \alpha to Ξ±.

12 Likes

Looking for an orgmode like link presentation
#56

Directly accept colors in add_regions

Importance: Major
Description: Change the add_regions method to accept rgb encoded colors. The function add_regions(key, [regions], <scope>, <icon>, <flags>) currently only accepts a scope and retrieves the color from the colorscheme. It would be nice, if it was possible to pass rbg encoded colors as an argument. This could either be done by changing the signature to accept an additional keyword argument <color> or by adding a flag sublime.SCOPE_IS_COLOR to treat the scope as a color.
Motivation: When creating a highlighted region one often just wants to define the color itself instead of a scope with a similar color.
At the moment some packages (e.g. SublimeLinter and ColorHighlighter) even change the colorscheme of the user to inject their colors. This has some disadvantages for the user and does not scale as a general solution.

17 Likes

Interface Suggestions
#57

Query contexts

A way to query a view for certain contexts. Much like a keybinding does, but within a plugin, to make the code easier to follow without reinventing the wheel. i.e. it would execute any on_query_context EventListeners behind the scenes.

view.query_context(key, operator, operand, match_all)

(maybe also include a region or point?)

operator is one of:

  • sublime.OP_EQUAL. Is the value of the context equal to the operand?
  • sublime.OP_NOT_EQUAL. Is the value of the context not equal to the operand?
  • sublime.OP_REGEX_MATCH. Does the value of the context match the regex given in operand?
  • sublime.OP_NOT_REGEX_MATCH. Does the value of the context not match the regex given in operand?
  • sublime.OP_REGEX_CONTAINS. Does the value of the context contain a substring matching the regex given in operand?
  • sublime.OP_NOT_REGEX_CONTAINS. Does the value of the context not contain a substring matching the regex given in operand?

match_all should be used if the context relates to the selections: does every selection have to match (match_all = True), or is at least one matching enough (match_all = False)?

Importance: Major

One potential advantage of this would be the ability to use PCRE flavor RegEx expressions instead of Python ones from within a plugin, which support a few extra features (like recursion).

11 Likes

Interface Suggestions
[test]: simulate key press
Programmatic way to query context
#59

Importance: Somewhat

Description: A hook for when text is about to be self-inserted, and a hook for when a key is pressed for which there is no key binding.

Motivation: There are no hooks for telling when text has been inserted. Therefore a number of things are not possible, including β€œinserting the same character N times” ala Emacs.

Having a hook called when there’s no binding for a key would allow us to implement ^Q which would insert the character that is normally bound to another command.

1 Like

Interface Suggestions
#60

Importance: Medium

Description: Make it possible to close a view on a modified file without requiring confirmation iff that same file is displayed in at least one other view.

Today if you split the window and and view the same file, and you want to close the window without saving, you are bothered with the confirmation even though there’s another view still open on the file.

3 Likes

#61

Test coverage reports for sublime syntax files

Mentioned by @FichteFoll here:

1 Like

Interface Suggestions
#62

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. """
  ...
5 Likes

#63

Prompt Open Recent Project

0 Likes

#64
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)
4 Likes

Interface Suggestions
#65

Keymap commands

class KeymapCommand(Command):

    def is_context(self):
        return False

    def run(self):
        pass

Usage

*.sublime-keymap

{ 
    "keys": ["tab"],
    "command": "command_name"
}
class CommandName(KeymapCommand):
    def is_context(self):
        # ...
    def run(self):
        # ...

Motivation

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.

0 Likes

Interface Suggestions
#68

Builtin logging

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.

Motivation

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.

6 Likes

split this topic #69

2 posts were split to a new topic: API Suggestion Discussion: Builtin logging

0 Likes

#70

Better control over the status bar

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:

  1. make status bar sections have a distinct and rearrangable section (drag with mouse). I’d like the β€œline x, column y” section to always be at the most left, for example, and the current git branch could move to the right next to encoding and line endings.
  2. define a callback for status bar entries that is called when they are clicked (left mouse button). Linters could display a list of the current errors with this, for example.
  3. define a context menu to be opened if the section is right-clicked. This could be a resource path to some .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

14 Likes

Retrieve the key for the default statusbar message
#71

Dynamic menu items

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")
15 Likes

#72

Find out where a setting value in view.settings() was defined

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"`

Corresponding github issues: #1003, #907

3 Likes

#74

Distinguish view types and get handles of special views

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:

7 Likes

split this topic #75

A post was split to a new topic: API Suggestion Discussion: EventListener for Project Changes

0 Likes

#76

Please also fix this very annoying settings bug after cloning a view:

Priority for me: major

1 Like

split this topic #77

2 posts were merged into an existing topic: On after modified event

0 Likes

#79

Snippets In on_query_completions

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__} )"
5 Likes