Sublime Forum

Table of Key Bindings

#19

The “current scope” use case.

As you already specced you can ask “Which bindings does e.g. the package MarkdownPlus define” (maybe none as it is discouraged to ship any by default).

The question I ask is: I’m in a file/buffer and want to ask which additional bindings are available here". E.g. you might overload ctrl+b (for build) per scope/syntax; or have the an auto formatter on ctrl+alt+shift+f for some languages but not for all. (And not the same!) Or you’re looking at .COMMITMSG or a “git diff” which has a “open in external diff viewer” binding …

For these “dynamic” bindings, sorting “additional” (un-common) bindings first might be the best UX. (E.g. likely every file that is not read-only has ctrl+s for save; that is not hard to memorize; but in a read only view o might be used to open a URL if the cursor is on a URL or to open the file if the cursor is in the git diff header.)

Does that make sense?

0 Likes

#20

Hi, @herr.kaste !

Thank you for taking the time to think about it and describe the use case here!

Does that make sense? Very much so! And if I am understanding your view correctly, as a Package developer, you’re looking for available (open) key combination “slots” that could be used, so need the full map (i.e. all currently-used key combinations that can be selected, showing combinations that are not bound) IN A PARTICULAR CONTEXT (i.e. the current context).

That makes COMPLETE sense, and so it calls for another command (over and above what I had planned thus far). It will be a little slower, but worth it:

KeyBindingReportInCurrentContextCommand

This command is exactly like KeyBindingReportCommand except that it does not include those key bindings that do not apply to the current context

Or…

Perhaps simply an additional Boolean argument to KeyBindingReportCommand on whether to exclude key bindings not applicable to the current view’s current scope where the first cursor is…

I think I’m going to go with the latter, because it can be an optional argument that is normally False, and if supplied, adds an additional filter… :smiley:

Steer me if I missed something, but it seems like a valuable option to me, and I’m working on the “filtering” code as I write this.

–Vic

0 Likes

#21

Forgive me if I do not enter the main discussion. I am the guy outside the engine room. But I thought that I might interject with an idea. I want to dispense with key bindings. They are like morse code which I have to remember. So using my previous thoughts, aired here (at one point earning a flag as OT), I am building a higher layer which I dub “ui-bindings”. That is, what do I see as I look at Sublime Text (or indeed any UI). My old brain can’t remember numerous key sequences so I shall instruct Subl Text to do my bidding. In my language. I am not the first … refer to Dijkstra who built layers on layers. So I prefer “klattu barada nikto” … commands driving Subl Text “ui-bindings”. In a crude way look at AutoKey (Qt) as a frontend to get an idea.

0 Likes

#22

You don’t need to use keybindings - they are an optional speed hack for commonly used functions. A well written plugin will also provide the same (and often, more) options via the command palette, so you can use fuzzy searches. Horses for courses. I regularly use both.

0 Likes

#23

Hi, @herr.kaste !

There is one thing I would like to verify: that when we are talking about “current scope”, down at the detail level, what I THINK is that we are really talking about this:

that every single condition listed in a key-binding's "context" entry is true,
thus causing Sublime Text to select that key in those exact circumstances.

that could perhaps be called “context applies to current circumstances of all indicated selections in current View”. I THINK this is true, because you are looking for “free key-binding slots”, even among keys that are bound in other circumstances/contexts.

Can you confirm?

Kind regards,
Vic

0 Likes

#24

With “current scope” I was really just talking natural language: where the cursor is, where my eyes are. I was not thinking about the implementation. But that is basically “current circumstances of all indicated selections in current view”.

About additional or free-slots. Here I actually meant: “special” bindings as compared to common bindings. Just as a user story: I already know and learned the common bindings, like e.g. “ctrl+s” or “ctrl+p”; but a special(ized) override is e.g. you enter box drawing mode and then all the arrow bindings suddenly apply and you likely want them to appear first or highlighted in some way. (Common bindings are at least the bindings Sublime ships with I guess.

Edit: order is maybe Default keymaps Sublime ships, Default User keymap, Package provided keymaps (often for special views or they apply in some special editor mode).
)

I.e. you bind “alt+f1” to a “show bindings” command that opens a bottom panel with a stylized table showing the current active key bindings. You also apply some bindings to that specialized view so we can filter or sort the table quickly, e.g. left/right to show different table states/pages, or holding ctrl to only show bindings with ctrl. Endless ways to make that dynamic. Like binding ctrl+r to search for bindings etc.

Just ideas, FWIW.

0 Likes

#25

Awesome! Thank you!

Afterthought: I just realized this is going to involve slurping up and actually RUNNING the whole collection of custom on_query_context listeners… Fortunately, the sublime-context Package for ST2’s context.Context._create_listeners() shows how it was done then. Hopefully most of it will still be applicable.

0 Likes

#26

Make sure to also read sublime.py and/or sublime_plugin.py because the registered event listeners (IIRC) are stored there. Obviously this is private information but it is stable and stable enough for a first release/POC anyway.

I thought calling on_query_context must be very easy as everything is declared in the key binding (operator: equals and operand: true and match_all: false are the defaults (IIRC)). But now I realize that all built-in “keys” are likely not available in Python but in the C++ core. So there might be some non-trivial rocks to climb here.

0 Likes

#27

I see the sublime_plugin.on_query_context() does a neat job of handling the live by-key queries.

Am I going to need more than on_query_context() ?

Re the latter, I did see a few esoteric context conditions that I thought might be impossible to test for, like:

Application:

  • is_recording_macro
  • last_command
  • last_modifying_command

Snippet State Machine:

  • has_next_field
  • has_prev_field

though has_snippet should be easy to determine by slurping up and parsing the “visible” Snippet files. In my “documentation” project, I re-grouped these “conditions/tests” by functionality to help work out an appropriate architecture for the code.

And at the moment, I’m thinking it’s going to be okay not to support those.

–Vic

0 Likes

#28

I guess sublime_plugin.on_query_context is the API endpoint ST actually calls from their C.

Yeah, these few context keys spring to mind but you do have command_history(index: int, modifying_only=False).

overlay_visible might be impossible and following_text and preceding_text likely have surprising edge case behavior (because these are old and were never changed for compat reasons).

0 Likes

#29

Wow, I didn’t realize command_history() was there! Nice… I’m actually familiar with the edge cases for following_text and preceding_text, having just finished v1.0 of the BoxDrawing Package: it happens at EOF, where a the behavior of a few things change, easily managed by keeping track of view.size() and the point being worked with.

0 Likes

#30

@herr.kaste a sneak peek at some debugging output…

In Context.query()...
  BoxDrawing/resources/keymaps/Default (Windows).sublime-keymap
  { ['alt+left'], box_drawing_draw_one_character({'direction': 3, 'line_count': 1})
    "context": [
      { "key": "box_drawing.ok_to_draw", "match_all": True }
    ]
  }
  In Context._condition_test()...
    <ContextCondition { "key": "box_drawing.ok_to_draw", "match_all": True }>
  Non-standard test [box_drawing.ok_to_draw]; consulting event listeners...
  False reported by <BoxDrawing.src.contexts.BoxDrawingContextEventListener object at 0x000001DEF4B78880>.
  Excluding this binding:  all_tests_passed=False

Getting the correct list of on_query_context() listeners is tricky, because in plugins that have multiple modules (living below the top-level of the Package), they have to be imported to the top level Plugin and so actually live in multiple modules! The tricky part was A) detecting this, and B) working how how to remove duplicates so there was only one, done with class names, e.g.

  • DocumentSyncListener (from LSP.boot module)
  • OverrideAuditContextListener (from OverrideAudit.override_audit module)
  • SyntaxTestHighlighterListener (from PackageDev.main module)

:slight_smile:

Also the sublime-context Package from ST2 had a lot of bugs in it, the most prominent of which was that it was only getting subclasses of EventListener, but overlooking subclasses of ViewEventListener

_on_qry_context_listeners() stats:
  modules_considered                      :    518
  modules_skipped_due_to_package          :    256
  modules_skipped_due_to_loading_exception:     65
  modules_skipped_due_to_being_st_modules :      3
  modules_loaded                          :    194
  attributes_examined                     :   6323
  attr_skipped_due_to_not_having_listeners:   6312
  listeners_instantiated_and_kept         :      5
  event_listeners                         :      1
  view_event_listeners                    :      4
0 Likes

#31

Hi, @herr.kaste!

You were right about overlay_visible and I think this is true of the other overlay... context key values (test names) as well.

Other than that, I’m ALMOST done implementing the context queries and have been testing along the way. (Consulting the list of live on_query_context() functions was implemented Saturday and works beautifully!) The one remaining that I am having trouble with is panel_has_focus. If I could just get a reference to a view when it is part of the UI (e.g. the input View at the bottom of the Console Panel, or the Find panels, etc.). If I could get that, then I could take it the rest of the way with:

    result = False
    element = view.element()
    print(f'{type(element)=}')

    if element:
        panel_detect_list = [
            'console:',
            'goto_anything:',   # Move to overlay has focus if this works.
            'command_palette:', # Move to overlay has focus if this works.
            'find:',
            'incremental_find:',
            'replace:',
            'find_in_files:',
            'input:',
            'exec:',
            'output:',
        ]

        for pdstr in panel_detect_list:
            if pdstr in element:
                test_val = True
                result = _evaluate_test(test_val, operator, operand)
                break

    return result

Do you know how to get a reference to the Views that play a role in the UI, attached to a Panel instead of to a Sheet?

Kind regards,
Vic

0 Likes

#32

Hm, do you already have an EventListener and listen for on_activated/deactivated? They fire for panel buffers as well as all other views (IIRC). I usually have a little state where I keep track of that if I need that information. I have these (non-async!) handlers very, very thin, e.g. just store the active view or active window or viewport and then do any computation when needed, lazy and/or on the “async”/worker thread.

Then you have e.g. https://www.sublimetext.com/docs/api_reference.html#sublime.View.element to ask what type a view is; IIRC they don’t change their type, so if you once detected which id a panel, find input box etc has they never change; likely that’s per Window of course but still. It is recommended to call view api function like view.element() while being on the UI/main thread as the worker thread would need a lock on the view anyways (which it already has running on the UI/blocking thread).

sublime-context Package from ST2 had a lot of bugs

I thin ST2 only had EventListeners and ViewEventListener was invented for ST3.

How are these bindings ordered? A while back, it was User package first, then unpacked packages, then packed/zipped packages, for both: py33 before py38. Packages sorted in reversed alpha sort (not casefold). But I now do wonder if you can get a better picture by just observing sublime_plugin.py’s internal state.

0 Likes

#33

Excellent idea!

It also dawned on me as I was waking up this morning that the “real” current view comes with TEXT COMMANDS! I remember having seen it in BoxDrawing and I put something in that Package to avoid permitting box drawing in the various Panels and Overlays. I’ve been using an ApplicationCommand! And of course window.active_view() only gives views attached to Sheets! And sure enough, it works! and I THINK that is also going to solve 2 the “overlay_has_focus” tests. Check this out (from a quick test):

Console:

window.views()

produces:

>>> window.views()
[View(13), View(33), View(22), View(14), View(16), View(18), View(19), View(23)]

TestTextCommand:

        print(self.view)
        print(self.view.element())

Produces (when focus is on Console input View):

View(34)     // <-- note this is not in the "active views" list.
console:input

Solved! :partying_face:

I think ST2 only had EventListeners and ViewEventListener was invented for ST3.

That would account for what I saw!

About ordering, lucky for us that keymap_paths = sublime.find_resources('*.sublime-keymap') appears to be returning them in initialization-time LOAD ordering, which I have been purposely taking advantage of in ordering of data collected for each report! :slight_smile:

P.S.

While I was collecting those ViewEventLister objects, I also noted that they “already know the View they are listening for” in an attribute, and I’m updating that too so they have the current View (at the beginning of each report).

    context.update_view_event_listeners(self.view)

P.P.S.

I also got an answer on what “panel_type” is—found it in Default (Windows).sublime-keymap!

I don’t know what OTHER values it might have, but it appears in 6 key bindings combined with the panel_has_focus test like this (shows possible values of “find” and “input”):

	{ "keys": ["alt+c"], "command": "toggle_case_sensitive", "context":
		[{"key": "panel_type", "operand": "find"}, {"key": "panel_has_focus"}],
	},
	{ "keys": ["enter"], "command": "select", "context":
		[
			{ "key": "panel_has_focus", "operator": "equal", "operand": true },
			{ "key": "panel_type", "operand": "input"},
		],
	},

And testing is showing Sublime Text defines:

  • “input” = (didn’t know how to test this one, but I am guessing any panel created by window.create_io_panel())
  • “find” = any input View on any of these Panels:
    • Find
    • Find-in-Files
    • Replace
    • Incremental Find
  • “output” = output of a Build System, but not of the console (probably any output Panel), which means any panel created with window.create_output_panel().

*grrrrrr I wish this stuff was documented… :frowning: *

A look into the list produced by window.panels() contains ONLY the above Find panels plus input and output panels, and the console, which seems to be an exception. I noted in the view.element() API documentation that it says the console output View is not accessible via the API. So the above kind of makes sense.

And testing with “exec” didn’t succeed, so it does not appear to be in the above list.

P.P.P.S.

…and just to document it: the possible values for “overlay_name” are (as proven in testing it with Sublime Text response to an actual key binding):

  • “goto”, and
  • “command_palette”

“goto_anything” does not work.

0 Likes

#34

Goodness, I love this editor!! :smile:

0 Likes

#35

Final results for CONTEXT logic:

All implemented, tested and working except:

  • has_next_field (I found no approach to implement it with current API)
  • has_prev_field (I found no approach to implement it with current API)
  • is_recording_macro (I found no approach to implement it with current API, though there is a way to get info about a macro already recorded)
  • overlay_visible (partial implementation)

With overlay_visible, it works correctly for Goto-Anything Overlay because it closes when you move focus back to the editing area (or anywhere else). However, it does not work for the Command Palette since it “lingers” when focus is moved elsewhere, or rather, it works when it has focus, and not when it doesn’t.

And that is the conclusion of the CONTEXT logic! I’m building it like a library so it can be re-used elsewhere.

Now for the output logic… and documenting everything I have learned so far that isn’t documented elsewhere.

0 Likes

#36

Which license applies to your docs (http://crystal-clear-research.com/docs/quickrefs/sublime_text/), I only see a copyright notice. Asking as there might be things that could be added to https://docs.sublimetext.io/ (I did only see that you seem to have written quite much). If you could put it under a free license, your work could be reused.

1 Like

#37

The above answers my puzzle as a sole experimenter. I was puzzling how do I put these ideas to use? These are very comprehensive works to learn from. My thoughts are to experiment with building a higher layer of UI-bindings. Not based on keys memory. Thanks for the contribution.

1 Like

#38

Hi, @milkman!

Good question. Because this is such an important topic, I have moved it here. I look forward to discussing this with you and others who are involved at the Sublime HQ decision-making level.

Kind regards,
Vic

0 Likes