Sublime Forum

show_quick_panel API

#1

Hello,
I start to work on plugin that must help me with “TODO” in my projects. This is my first plugin and work with python and github. Source.
I faced with leak of documentation :frowning: . I use show_quick_panel to display list of TODOs in file and I’d like to do it like goto_anithing : when you focus list item it scrolls the view to that item.

1 Like

#2

Here is some code that I use; it should point you in the right direction.

[code]def isView(view_id):
# check that they are in a View, rather than some panel, etc.
if not view_id: return False
window = sublime.active_window()
view = window.active_view() if window != None else None
return (view is not None and view.id() == view_id)

def showRegion(view, reg):
view.sel().clear()
view.sel().add(reg)
view.show(reg)

class QuickEditsCommand(sublime_plugin.TextCommand):
# Shows a quick panel to jump to edit lines.
def run(self, edit):
self.vid = self.view.id()
if not isView(self.vid):
sublime.status_message(‘Click into the view/tab first.’)
return
edited = adjustEdits(self.view)
if not edited:
sublime.status_message(‘No edits to list.’)
return
the_edits = getEditList(self.view, edited)
if the_edits:
sublime.active_window().show_quick_panel(the_edits, self.on_chosen)

def on_chosen(self, index):
    if index == -1: return
    if not isView(self.vid):
        sublime.status_message('You are in a different view.')
        return
    edited = self.view.get_regions("edited_rgns") or ]
    for reg in [r for i, r in enumerate(edited) if i == index]:
        showRegion(self.view, reg)
        break[/code]
0 Likes

Get index of show_quick_panel rather than redirect to another function
#3

I don’t know if my getEditList function below is also useful, but it constructs a list (with line numbers, etc.) that is used for display in the quick panel.

def getEditList(view, edited): the_edits = ] curr_edit = view.get_regions("edited_rgn") or ] curr_edit = curr_edit[0] if curr_edit else None for i, r in enumerate(edited): curr_line, _ = view.rowcol(r.begin()) curr_text = view.substr(r).strip():50] if not len(curr_text): curr_text = view.substr(view.line(r)).strip():50] + " (line)" if curr_edit and r.intersects(curr_edit): display_line = "*%03d %s" % (curr_line + 1, curr_text) else: display_line = " %03d %s" % (curr_line + 1, curr_text) the_edits.append(display_line) return the_edits

0 Likes

#4

Thanks. But this part I already have. I interested in some onfocus event for show_quick_panel, not only onselect.

0 Likes

#5

Sorry, I mis-read - must be tired.

0 Likes

#6

You can use the **on_modified **event to read keystrokes in the quick-panel. You need some way of recognising, though, that the quick-panel is active. show_quick_panel does not return a reference so it might be a little tricky to confirm.

Using the id is probably easiest; it might require a process of elimination though. That is, get all the ids for the views - if the current id is not in this list then deduce that it is the quick panel?! You could do this just before, and just after, showing the quick-panel! My isView function may still be of use.

Added: Actually, just after showing the quick-panel you could just store the id of the current view(?).

I don’t know if there is any easier way to confirm that the current view is a quick-panel :question:.

You can use show() or show_at_center() to scroll the view. If you do anything else though you may need to make sure that you focus back on the quick-panel.

Good luck, Andy.

0 Likes

#7

Heh, after some digging I found next function useful to determine panel (any panel):

def isPanel(view): (group, index) = view.window().get_view_index(view) if group is -1 and index is -1: return True return False
But I still have no luck to detect focus changes in this panel, up and down arrows does not rises on_modified or on_selection_modified events :frowning:
I start to think that goto_anithing uses some internal mechanisms not available in Python API.

P.S. Thansk for helping, will continue diggin.

0 Likes

#8

Here are my thoughts and tests on this (warning: this post is long):

At first I tried to write a function that opens a quick panel and periodically checks until it has opened to write a global flag and id. From this id I could determine in an event listener if the currently “modified” view is the quick panel or just some other view/panel I don’t care about. I started using the thread module for this but calling window.active_view().id() raised a RuntimeError and told me it cannot be run in a thread other than the main thread. This is the code with sublime.set_timeout():

import sublime
import sublime_plugin
import sublime_lib



quick_panel_active = False
quick_panel_id = 0


def quick_panel(w, items, callback, flags=0):
    global quick_panel_active
    timeout = 2000

    def find_quick_panel_id(w, current_id, time=0):
        global quick_panel_active, quick_panel_id

        vid = w.active_view().id()
        print("checking panel", vid)
        if vid != current_id:
            quick_panel_active = True
            print("panel found")
            quick_panel_id = vid

        if time >= timeout:
            print("timeout")
            return

        # recall recursively
        wait = 10
        sublime.set_timeout(lambda: find_quick_panel_id(w, current_id, time + wait), wait)

    quick_panel_active = False  # what if there's still a quick paneL active?
    current_id = w.active_view().id()
    print("cid", current_id)

    sublime.set_timeout(lambda: find_quick_panel_id(w, current_id), 0)
    w.show_quick_panel(items, callback, flags)


class TestCommandCommand(sublime_plugin.WindowCommand):
    string = "abcdefghijklmnopqrstuvwxyz"
    choose = [c * 3 for c in string]

    def is_checked(self):
        return True

    def run(self):
        quick_panel(self.window, self.choose, self.on_done)

    def on_done(self, result):
        print("result", result, self.choose[result] if result != -1 else None)

Turned out that window.active_view() does not return panels. Code for testing this behaviour with different panels focused (using sublime_lib.WindowAndTextCommand:

class ActiveViewCommand(sublime_lib.WindowAndTextCommand):
    def run(self, param=None):
        print("window command", self._window_command, "param", param)
        print("  view", self.view, self.view.id())
        print(" aview", self.window.active_view(), self.window.active_view().id())
        print("aaview", sublime.active_window().active_view(), sublime.active_window().active_view().id())

Using an event listener on “on_modified” - which seems to be the only one fired by panels and thus the only method to get a panel’s view object - I could get it to work somehow (using sublime_lib.view.get_text):

import sublime_plugin
import sublime_lib
vlib = sublime_lib.view

last_view_id = 0
quick_panel_waiting = False
quick_panel_active  = False
quick_panel_id = 0


class TestCommandCommand(sublime_lib.WindowAndTextCommand):
    string = "abcdefghijklmnopqrstuvwxyz"
    choose = [c * 3 for c in string]

    def is_checked(self):
        return True

    def run(self, param=None):
        global last_view_id, quick_panel_waiting

        last_view_id = self.view.id()
        quick_panel_waiting = True
        self.window.show_quick_panel(self.choose, self.on_done)

    def on_done(self, result):
        print("result", result, self.choose[result] if result != -1 else None)


class QuickPanelListener(sublime_plugin.EventListener):

    def on_modified(self, view):
        global quick_panel_active, quick_panel_waiting, quick_panel_id

        if quick_panel_waiting and view.id() != last_view_id:
            quick_panel_active  = True
            quick_panel_waiting = False
            quick_panel_id = view.id()

        if quick_panel_active and view.id() != quick_panel_id:
            quick_panel_active = False

        if quick_panel_active:
            print("text in panel", vlib.get_text(view))

However, this does only work if you actually enter something in the quick panel. If you close the panel instantly with “esc” and then enter something in the console it will be detected as the quick panel instead and there is no way to work around this.

I didn’t have any success with using various keybindings (note: I used many different values in the “panel” context). The dummy command just printed something in the console.


    { "keys": "down"], "command": "dummy",
        "context": 
            {"key": "panel", "operand": "console"},
            {"key": "panel_has_focus"}
        ]
    },
    { "keys": "up"], "command": "dummy",
        "context": 
            { "key": "overlay_visible", "operator": "equal", "operand": true }
        ]
    }
]

Well, this is how I spent my last few hours without accomplishing anything. Furthermore, even after getting either of these attempts to work you would need both of them to combine the input and trim down the list. And even that would not be enough because the panel uses the fuzzy search for text input and the selected item does not change when you alter the text and it still matches the selected item.
We need a new API component, maybe an “on_select” callback for the show_quick_panel method, to accomplish the awesome live-update from the goto “overlay”.

1 Like

#9

Thanks for your trying FitchteFoll. I spent some time digging into keybindigns and find that this is a bug? Below you can see some code from Default (Windows).sublime-keymap file.

[code] { “keys”: “escape”], “command”: “hide_panel”, “args”: {“cancel”: true},
“context”:

		{ "key": "panel_visible", "operator": "equal", "operand": true }
	]
},
{ "keys": "escape"], "command": "hide_overlay", "context":
	
		{ "key": "overlay_visible", "operator": "equal", "operand": true }
	]
}[/code]

First check : change command into some other and test it.
Result : panel didn’t closing, overlay (i.e. “goto anithing” ) works as before.
Second check : open console and write “sublime.log_commands(1)” and test it.
Result : panel logs some “hide_panel” command, overlay logs nothing.

**Update : **in change notes to ST3 i see solution :smile:

0 Likes