vibefpmgui.command_palette

View → Command Palette… (⌘⇧P / Ctrl+Shift+P): every command in the menus, found by typing part of its name, as in VS Code.

The commands aren't listed here: they're the menu bar's actions, read when the palette opens (collect), so a command added to a menu is in the palette too. A menu that fills itself when it opens (Syntax Colors, Window Style, Recent Projects…) is asked to, as it is when it's opened. Running a command triggers its action, as choosing it in its menu does.

  1"""View → Command Palette… (⌘⇧P / Ctrl+Shift+P): every command in the menus,
  2found by typing part of its name, as in VS Code.
  3
  4The commands aren't listed here: they're the menu bar's actions, read when
  5the palette opens (`collect`), so a command added to a menu is in the
  6palette too. A menu that fills itself when it opens (Syntax Colors, Window
  7Style, Recent Projects…) is asked to, as it is when it's opened. Running a
  8command triggers its action, as choosing it in its menu does.
  9"""
 10
 11from __future__ import annotations
 12
 13import re
 14from typing import NamedTuple
 15
 16from qtpy.QtCore import QEvent, QRect, QSize, Qt, QTimer
 17from qtpy.QtGui import QAction, QColor, QFontMetrics, QIcon, QKeySequence, QPalette
 18from qtpy.QtWidgets import (
 19    QApplication,
 20    QFrame,
 21    QLineEdit,
 22    QListWidget,
 23    QListWidgetItem,
 24    QMenu,
 25    QMenuBar,
 26    QStyle,
 27    QStyledItemDelegate,
 28    QVBoxLayout,
 29    QWidget,
 30)
 31
 32SEPARATOR = " › "
 33MAX_RECENT = 12
 34COMMAND_ROLE = Qt.UserRole + 1  # an item's Command
 35
 36
 37class Command(NamedTuple):
 38    path: tuple[str, ...]  # its menus' titles, then its own ("View", "Theme", "Nord")
 39    action: QAction
 40
 41    @property
 42    def name(self) -> str:
 43        return self.path[-1]
 44
 45    @property
 46    def title(self) -> str:
 47        return SEPARATOR.join(self.path)
 48
 49
 50def clean(text: str) -> str:
 51    """A menu text as shown: no "&" for its mnemonic ("&&" is an "&"), no
 52    tab and what's after it (a shortcut some menus put in their text)."""
 53    text = text.split("\t", 1)[0]
 54    return re.sub(r"&(.)", r"\1", text).strip()
 55
 56
 57def collect(menu_bar: QMenuBar) -> list[Command]:
 58    """Every command in the menu bar's menus, in menu order: not separators,
 59    submenus themselves or hidden actions.
 60
 61    A submenu is found from its menu (``QMenu.menuAction()``), never with
 62    ``QAction.menu()``: in PySide that leaves a menu the menu bar made (and
 63    its actions) deleted as far as Python knows, which broke the app's own
 64    menus."""
 65    commands: list[Command] = []
 66    seen: set[int] = set()
 67    submenus = {m.menuAction(): m for m in menu_bar.window().findChildren(QMenu)}
 68
 69    def walk(menu: QMenu, path: tuple[str, ...]):
 70        menu.aboutToShow.emit()  # (a menu filled when it opens: filled now, as then)
 71        submenus.update({m.menuAction(): m for m in menu.findChildren(QMenu)})  # (made by filling it)
 72        for action in menu.actions():
 73            if action.isSeparator() or not action.isVisible() or not clean(action.text()):
 74                continue
 75            here = (*path, clean(action.text()))
 76            submenu = submenus.get(action)
 77            if submenu is not None:
 78                walk(submenu, here)
 79            elif id(action) not in seen:
 80                seen.add(id(action))
 81                commands.append(Command(here, action))
 82
 83    for action in menu_bar.actions():
 84        submenu = submenus.get(action)
 85        if submenu is not None and action.isVisible():
 86            walk(submenu, (clean(action.text()),))
 87    return commands
 88
 89
 90def _word_starts(text: str) -> set[int]:
 91    return {0} | {i for i in range(1, len(text)) if not text[i - 1].isalnum() and text[i].isalnum()}
 92
 93
 94def _subsequence(word: str, text: str, starts: set[int], prefer: bool = True) -> int | None:
 95    """How well ``word``'s letters appear, in order, in ``text`` (None: they
 96    don't): each letter counts, more at a word's start or right after the
 97    one before. Each letter goes at the next word's start that has it, if
 98    any (``prefer``); if that leaves a later letter unmatched, at the next
 99    place it is."""
100    score, at, last, first = 0, 0, -2, None
101    for ch in word:
102        found = text.find(ch, at)
103        if found < 0:
104            return None if prefer is False else _subsequence(word, text, starts, prefer=False)
105        if prefer and found != last + 1 and found not in starts:
106            later = found
107            while later >= 0 and later not in starts:
108                later = text.find(ch, later + 1)
109            if later >= 0:
110                found = later
111        gap = found - last - 1 if last >= 0 else 0
112        score += 1 + (3 if found in starts else 0) + (2 if found == last + 1 else 0) - min(gap, 4)
113        first = found if first is None else first
114        last, at = found, found + 1
115    if first is not None and last - first + 1 > max(3 * len(word), len(word) + 8):
116        return None  # (its letters scattered far apart: not what was meant)
117    return score
118
119
120def score(query: str, command: Command) -> int | None:
121    """How well a command matches what's typed (None: it doesn't). Each word
122    typed must match, letters in order (so "mvup" finds Move Line Up); in
123    the command's own name counts more than in its menus' names."""
124    words = query.lower().split()
125    if not words:
126        return 0
127    name, title = command.name.lower(), command.title.lower()
128    name_starts, title_starts = _word_starts(name), _word_starts(title)
129    total = 0
130    for word in words:
131        in_name = _subsequence(word, name, name_starts)
132        in_title = _subsequence(word, title, title_starts)
133        if in_name is None and in_title is None:
134            return None
135        total += max(in_name * 2 if in_name is not None else 0, in_title or 0)
136        if name.startswith(word):
137            total += 5
138    if name.rstrip("…") == " ".join(words):
139        total += 10  # (just its name: it before others that start the same)
140    return total
141
142
143REVEAL_LETTERS = 5  # a hidden command's name: at least this much of it typed, to show it
144
145
146def revealed(query: str, command: Command) -> bool:
147    """Whether a hidden command shows for what's typed: the start of its name,
148    at least REVEAL_LETTERS letters of it (spaces and case aside)."""
149    typed = "".join(query.lower().split())
150    return len(typed) >= REVEAL_LETTERS and "".join(command.name.lower().split()).startswith(typed)
151
152
153def ranked(query: str, commands: list[Command], recent: list[str]) -> list[Command]:
154    """The commands matching ``query``, best first: the best match, then the
155    most recently used, then those available now (the others are listed,
156    greyed), then the fewest menus deep, then the shortest name, then menu
157    order. Nothing typed: the
158    recently used first, then the menus' order."""
159    order = {title: n for n, title in enumerate(recent)}
160    matched = []
161    for index, command in enumerate(commands):
162        points = score(query, command)
163        if points is not None:
164            matched.append((-points, order.get(command.title, len(recent)), not command.action.isEnabled(),
165                            len(command.path), len(command.name), index, command))
166    matched.sort(key=lambda m: m[:6] if query.strip() else (m[1], m[2], m[5]))
167    return [m[-1] for m in matched]
168
169
170class _ItemDelegate(QStyledItemDelegate):
171    """A command: its menus' names muted, then its own; a tick if it's
172    checked; its shortcut on the right."""
173
174    def sizeHint(self, option, index):
175        return QSize(option.rect.width(), QFontMetrics(option.font).height() + 10)
176
177    def paint(self, painter, option, index):
178        command: Command = index.data(COMMAND_ROLE)
179        style = option.widget.style() if option.widget else QApplication.style()
180        style.drawPrimitive(QStyle.PE_PanelItemViewItem, option, painter, option.widget)
181        selected = bool(option.state & QStyle.State_Selected)
182        pal = option.palette
183        text = pal.color(QPalette.HighlightedText if selected else QPalette.Text)
184        muted = QColor(text)
185        muted.setAlpha(150)
186        if not command.action.isEnabled():
187            text.setAlpha(110)
188            muted.setAlpha(80)
189        fm = QFontMetrics(option.font)
190        rect = option.rect.adjusted(10, 0, -10, 0)
191        painter.save()
192        shortcut = command.action.shortcut().toString(QKeySequence.NativeText)
193        if shortcut:
194            painter.setPen(muted)
195            painter.drawText(rect, Qt.AlignRight | Qt.AlignVCenter, shortcut)
196            rect.setRight(rect.right() - fm.horizontalAdvance(shortcut) - 16)
197        x = rect.left()
198        tick = "✓ " if command.action.isCheckable() and command.action.isChecked() else ""
199        menus = SEPARATOR.join(command.path[:-1]) + SEPARATOR if len(command.path) > 1 else ""
200        for part, color in ((tick, text), (menus, muted), (command.name, text)):
201            if not part:
202                continue
203            painter.setPen(color)
204            width = rect.right() - x
205            shown = fm.elidedText(part, Qt.ElideMiddle, max(0, width))
206            painter.drawText(QRect(x, rect.top(), max(0, width), rect.height()), Qt.AlignLeft | Qt.AlignVCenter,
207                             shown)
208            x += fm.horizontalAdvance(shown)
209        painter.restore()
210
211
212class CommandPalette(QFrame):
213    """The palette: a field to type in, the matching commands under it.
214    ``recent`` (the titles of the commands last run, latest first) is kept
215    up to date in place; ``recent_changed`` (if set) is called after."""
216
217    WIDTH = 620
218    ROWS = 14
219
220    def __init__(self, window: QWidget, recent: list[str] | None = None):
221        super().__init__(window, Qt.Popup)
222        self.window_ = window
223        self.recent = recent if recent is not None else []
224        self.recent_changed = None
225        self.commands: list[Command] = []
226        self.hidden: list[Command] = []  # (in no menu: shown only when their name is typed, see revealed)
227        self.setFrameShape(QFrame.StyledPanel)
228        self.field = QLineEdit()
229        self.field.setPlaceholderText("Type a command (letters in order: \"mvup\" finds Move Line Up)")
230        self.field.setClearButtonEnabled(True)
231        self.field.textChanged.connect(self._filter)
232        self.field.installEventFilter(self)
233        # Its icon, in front of what's typed (the menu item's: see set_icon).
234        self.icon_action = self.field.addAction(QIcon(), QLineEdit.LeadingPosition)
235        self.list = QListWidget()
236        self.list.setItemDelegate(_ItemDelegate(self.list))
237        self.list.setUniformItemSizes(True)
238        self.list.setFocusPolicy(Qt.NoFocus)
239        self.list.itemClicked.connect(lambda item: self.run(item.data(COMMAND_ROLE)))
240        layout = QVBoxLayout(self)
241        layout.setContentsMargins(6, 6, 6, 6)
242        layout.setSpacing(6)
243        layout.addWidget(self.field)
244        layout.addWidget(self.list)
245
246    def set_icon(self, icon: QIcon):
247        """The icon in front of the field (the menu item's, in the theme's colors)."""
248        self.icon_action.setIcon(icon)
249
250    def open(self):
251        """Read the menus, and show the palette at the top of the window."""
252        menu_bar = self.window_.menuBar() if hasattr(self.window_, "menuBar") else None
253        self.commands = collect(menu_bar) if menu_bar is not None else []
254        self.field.clear()
255        self._filter("")
256        width = min(self.WIDTH, max(300, self.window_.width() - 40))
257        row = self.list.sizeHintForRow(0) if self.list.count() else 24
258        self.resize(width, self.field.sizeHint().height() + row * self.ROWS + 24)
259        top = self.window_.mapToGlobal(self.window_.rect().topLeft())
260        self.move(top.x() + (self.window_.width() - width) // 2, top.y() + 40)
261        self.show()
262        self.raise_()
263        self.field.setFocus()
264
265    def _filter(self, text: str):
266        self.list.clear()
267        shown = self.commands + [c for c in self.hidden if revealed(text, c)]
268        for command in ranked(text, shown, self.recent):
269            item = QListWidgetItem(command.title)
270            item.setData(COMMAND_ROLE, command)
271            if not command.action.isEnabled():
272                item.setToolTip("Not available now")
273            elif command.action.statusTip():
274                item.setToolTip(command.action.statusTip())
275            self.list.addItem(item)
276        if self.list.count():
277            self.list.setCurrentRow(0)
278
279    def eventFilter(self, obj, event):
280        if obj is self.field and event.type() == QEvent.KeyPress:
281            key = event.key()
282            steps = {Qt.Key_Up: -1, Qt.Key_Down: 1, Qt.Key_PageUp: -self.ROWS, Qt.Key_PageDown: self.ROWS}
283            if key in steps and self.list.count():
284                row = max(0, min(self.list.count() - 1, self.list.currentRow() + steps[key]))
285                self.list.setCurrentRow(row)
286                return True
287            if key in (Qt.Key_Return, Qt.Key_Enter):
288                item = self.list.currentItem()
289                if item is not None:
290                    self.run(item.data(COMMAND_ROLE))
291                return True
292            if key == Qt.Key_Escape:
293                self.close()
294                return True
295        return super().eventFilter(obj, event)
296
297    def run(self, command: Command):
298        """Run a command (if it's available now): the palette closes first, so
299        the command acts where the focus was (e.g. the editor)."""
300        if not command.action.isEnabled():
301            return
302        title = command.title
303        if title in self.recent:
304            self.recent.remove(title)
305        self.recent.insert(0, title)
306        del self.recent[MAX_RECENT:]
307        if self.recent_changed is not None:
308            self.recent_changed(self.recent)
309        self.close()
310        QTimer.singleShot(0, self.window_, command.action.trigger)
311
312    def current_titles(self) -> list[str]:
313        """(The titles listed, in order: for the tests.)"""
314        return [self.list.item(i).data(COMMAND_ROLE).title for i in range(self.list.count())]
SEPARATOR = ' › '
MAX_RECENT = 12
COMMAND_ROLE = 257
class Command(typing.NamedTuple):
38class Command(NamedTuple):
39    path: tuple[str, ...]  # its menus' titles, then its own ("View", "Theme", "Nord")
40    action: QAction
41
42    @property
43    def name(self) -> str:
44        return self.path[-1]
45
46    @property
47    def title(self) -> str:
48        return SEPARATOR.join(self.path)

Command(path, action)

Command(path: tuple[str, ...], action: QAction)

Create new instance of Command(path, action)

path: tuple[str, ...]

Alias for field number 0

action: PySide6.QtGui.QAction

Alias for field number 1

name: str
42    @property
43    def name(self) -> str:
44        return self.path[-1]
title: str
46    @property
47    def title(self) -> str:
48        return SEPARATOR.join(self.path)
def clean(text: str) -> str:
51def clean(text: str) -> str:
52    """A menu text as shown: no "&" for its mnemonic ("&&" is an "&"), no
53    tab and what's after it (a shortcut some menus put in their text)."""
54    text = text.split("\t", 1)[0]
55    return re.sub(r"&(.)", r"\1", text).strip()

A menu text as shown: no "&" for its mnemonic ("&&" is an "&"), no tab and what's after it (a shortcut some menus put in their text).

def collect( menu_bar: PySide6.QtWidgets.QMenuBar) -> list[Command]:
58def collect(menu_bar: QMenuBar) -> list[Command]:
59    """Every command in the menu bar's menus, in menu order: not separators,
60    submenus themselves or hidden actions.
61
62    A submenu is found from its menu (``QMenu.menuAction()``), never with
63    ``QAction.menu()``: in PySide that leaves a menu the menu bar made (and
64    its actions) deleted as far as Python knows, which broke the app's own
65    menus."""
66    commands: list[Command] = []
67    seen: set[int] = set()
68    submenus = {m.menuAction(): m for m in menu_bar.window().findChildren(QMenu)}
69
70    def walk(menu: QMenu, path: tuple[str, ...]):
71        menu.aboutToShow.emit()  # (a menu filled when it opens: filled now, as then)
72        submenus.update({m.menuAction(): m for m in menu.findChildren(QMenu)})  # (made by filling it)
73        for action in menu.actions():
74            if action.isSeparator() or not action.isVisible() or not clean(action.text()):
75                continue
76            here = (*path, clean(action.text()))
77            submenu = submenus.get(action)
78            if submenu is not None:
79                walk(submenu, here)
80            elif id(action) not in seen:
81                seen.add(id(action))
82                commands.append(Command(here, action))
83
84    for action in menu_bar.actions():
85        submenu = submenus.get(action)
86        if submenu is not None and action.isVisible():
87            walk(submenu, (clean(action.text()),))
88    return commands

Every command in the menu bar's menus, in menu order: not separators, submenus themselves or hidden actions.

A submenu is found from its menu (QMenu.menuAction()), never with QAction.menu(): in PySide that leaves a menu the menu bar made (and its actions) deleted as far as Python knows, which broke the app's own menus.

def score(query: str, command: Command) -> int | None:
121def score(query: str, command: Command) -> int | None:
122    """How well a command matches what's typed (None: it doesn't). Each word
123    typed must match, letters in order (so "mvup" finds Move Line Up); in
124    the command's own name counts more than in its menus' names."""
125    words = query.lower().split()
126    if not words:
127        return 0
128    name, title = command.name.lower(), command.title.lower()
129    name_starts, title_starts = _word_starts(name), _word_starts(title)
130    total = 0
131    for word in words:
132        in_name = _subsequence(word, name, name_starts)
133        in_title = _subsequence(word, title, title_starts)
134        if in_name is None and in_title is None:
135            return None
136        total += max(in_name * 2 if in_name is not None else 0, in_title or 0)
137        if name.startswith(word):
138            total += 5
139    if name.rstrip("…") == " ".join(words):
140        total += 10  # (just its name: it before others that start the same)
141    return total

How well a command matches what's typed (None: it doesn't). Each word typed must match, letters in order (so "mvup" finds Move Line Up); in the command's own name counts more than in its menus' names.

REVEAL_LETTERS = 5
def revealed(query: str, command: Command) -> bool:
147def revealed(query: str, command: Command) -> bool:
148    """Whether a hidden command shows for what's typed: the start of its name,
149    at least REVEAL_LETTERS letters of it (spaces and case aside)."""
150    typed = "".join(query.lower().split())
151    return len(typed) >= REVEAL_LETTERS and "".join(command.name.lower().split()).startswith(typed)

Whether a hidden command shows for what's typed: the start of its name, at least REVEAL_LETTERS letters of it (spaces and case aside).

def ranked( query: str, commands: list[Command], recent: list[str]) -> list[Command]:
154def ranked(query: str, commands: list[Command], recent: list[str]) -> list[Command]:
155    """The commands matching ``query``, best first: the best match, then the
156    most recently used, then those available now (the others are listed,
157    greyed), then the fewest menus deep, then the shortest name, then menu
158    order. Nothing typed: the
159    recently used first, then the menus' order."""
160    order = {title: n for n, title in enumerate(recent)}
161    matched = []
162    for index, command in enumerate(commands):
163        points = score(query, command)
164        if points is not None:
165            matched.append((-points, order.get(command.title, len(recent)), not command.action.isEnabled(),
166                            len(command.path), len(command.name), index, command))
167    matched.sort(key=lambda m: m[:6] if query.strip() else (m[1], m[2], m[5]))
168    return [m[-1] for m in matched]

The commands matching query, best first: the best match, then the most recently used, then those available now (the others are listed, greyed), then the fewest menus deep, then the shortest name, then menu order. Nothing typed: the recently used first, then the menus' order.

class CommandPalette(PySide6.QtWidgets.QFrame):
213class CommandPalette(QFrame):
214    """The palette: a field to type in, the matching commands under it.
215    ``recent`` (the titles of the commands last run, latest first) is kept
216    up to date in place; ``recent_changed`` (if set) is called after."""
217
218    WIDTH = 620
219    ROWS = 14
220
221    def __init__(self, window: QWidget, recent: list[str] | None = None):
222        super().__init__(window, Qt.Popup)
223        self.window_ = window
224        self.recent = recent if recent is not None else []
225        self.recent_changed = None
226        self.commands: list[Command] = []
227        self.hidden: list[Command] = []  # (in no menu: shown only when their name is typed, see revealed)
228        self.setFrameShape(QFrame.StyledPanel)
229        self.field = QLineEdit()
230        self.field.setPlaceholderText("Type a command (letters in order: \"mvup\" finds Move Line Up)")
231        self.field.setClearButtonEnabled(True)
232        self.field.textChanged.connect(self._filter)
233        self.field.installEventFilter(self)
234        # Its icon, in front of what's typed (the menu item's: see set_icon).
235        self.icon_action = self.field.addAction(QIcon(), QLineEdit.LeadingPosition)
236        self.list = QListWidget()
237        self.list.setItemDelegate(_ItemDelegate(self.list))
238        self.list.setUniformItemSizes(True)
239        self.list.setFocusPolicy(Qt.NoFocus)
240        self.list.itemClicked.connect(lambda item: self.run(item.data(COMMAND_ROLE)))
241        layout = QVBoxLayout(self)
242        layout.setContentsMargins(6, 6, 6, 6)
243        layout.setSpacing(6)
244        layout.addWidget(self.field)
245        layout.addWidget(self.list)
246
247    def set_icon(self, icon: QIcon):
248        """The icon in front of the field (the menu item's, in the theme's colors)."""
249        self.icon_action.setIcon(icon)
250
251    def open(self):
252        """Read the menus, and show the palette at the top of the window."""
253        menu_bar = self.window_.menuBar() if hasattr(self.window_, "menuBar") else None
254        self.commands = collect(menu_bar) if menu_bar is not None else []
255        self.field.clear()
256        self._filter("")
257        width = min(self.WIDTH, max(300, self.window_.width() - 40))
258        row = self.list.sizeHintForRow(0) if self.list.count() else 24
259        self.resize(width, self.field.sizeHint().height() + row * self.ROWS + 24)
260        top = self.window_.mapToGlobal(self.window_.rect().topLeft())
261        self.move(top.x() + (self.window_.width() - width) // 2, top.y() + 40)
262        self.show()
263        self.raise_()
264        self.field.setFocus()
265
266    def _filter(self, text: str):
267        self.list.clear()
268        shown = self.commands + [c for c in self.hidden if revealed(text, c)]
269        for command in ranked(text, shown, self.recent):
270            item = QListWidgetItem(command.title)
271            item.setData(COMMAND_ROLE, command)
272            if not command.action.isEnabled():
273                item.setToolTip("Not available now")
274            elif command.action.statusTip():
275                item.setToolTip(command.action.statusTip())
276            self.list.addItem(item)
277        if self.list.count():
278            self.list.setCurrentRow(0)
279
280    def eventFilter(self, obj, event):
281        if obj is self.field and event.type() == QEvent.KeyPress:
282            key = event.key()
283            steps = {Qt.Key_Up: -1, Qt.Key_Down: 1, Qt.Key_PageUp: -self.ROWS, Qt.Key_PageDown: self.ROWS}
284            if key in steps and self.list.count():
285                row = max(0, min(self.list.count() - 1, self.list.currentRow() + steps[key]))
286                self.list.setCurrentRow(row)
287                return True
288            if key in (Qt.Key_Return, Qt.Key_Enter):
289                item = self.list.currentItem()
290                if item is not None:
291                    self.run(item.data(COMMAND_ROLE))
292                return True
293            if key == Qt.Key_Escape:
294                self.close()
295                return True
296        return super().eventFilter(obj, event)
297
298    def run(self, command: Command):
299        """Run a command (if it's available now): the palette closes first, so
300        the command acts where the focus was (e.g. the editor)."""
301        if not command.action.isEnabled():
302            return
303        title = command.title
304        if title in self.recent:
305            self.recent.remove(title)
306        self.recent.insert(0, title)
307        del self.recent[MAX_RECENT:]
308        if self.recent_changed is not None:
309            self.recent_changed(self.recent)
310        self.close()
311        QTimer.singleShot(0, self.window_, command.action.trigger)
312
313    def current_titles(self) -> list[str]:
314        """(The titles listed, in order: for the tests.)"""
315        return [self.list.item(i).data(COMMAND_ROLE).title for i in range(self.list.count())]

The palette: a field to type in, the matching commands under it. recent (the titles of the commands last run, latest first) is kept up to date in place; recent_changed (if set) is called after.

CommandPalette(window: PySide6.QtWidgets.QWidget, recent: list[str] | None = None)
221    def __init__(self, window: QWidget, recent: list[str] | None = None):
222        super().__init__(window, Qt.Popup)
223        self.window_ = window
224        self.recent = recent if recent is not None else []
225        self.recent_changed = None
226        self.commands: list[Command] = []
227        self.hidden: list[Command] = []  # (in no menu: shown only when their name is typed, see revealed)
228        self.setFrameShape(QFrame.StyledPanel)
229        self.field = QLineEdit()
230        self.field.setPlaceholderText("Type a command (letters in order: \"mvup\" finds Move Line Up)")
231        self.field.setClearButtonEnabled(True)
232        self.field.textChanged.connect(self._filter)
233        self.field.installEventFilter(self)
234        # Its icon, in front of what's typed (the menu item's: see set_icon).
235        self.icon_action = self.field.addAction(QIcon(), QLineEdit.LeadingPosition)
236        self.list = QListWidget()
237        self.list.setItemDelegate(_ItemDelegate(self.list))
238        self.list.setUniformItemSizes(True)
239        self.list.setFocusPolicy(Qt.NoFocus)
240        self.list.itemClicked.connect(lambda item: self.run(item.data(COMMAND_ROLE)))
241        layout = QVBoxLayout(self)
242        layout.setContentsMargins(6, 6, 6, 6)
243        layout.setSpacing(6)
244        layout.addWidget(self.field)
245        layout.addWidget(self.list)

__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, frameShape: PySide6.QtWidgets.QFrame.Shape | None = None, frameShadow: PySide6.QtWidgets.QFrame.Shadow | None = None, lineWidth: int | None = None, midLineWidth: int | None = None, frameWidth: int | None = None, frameRect: PySide6.QtCore.QRect | None = None) -> None

Initialize self. See help(type(self)) for accurate signature.

WIDTH = 620
ROWS = 14
window_
recent
recent_changed
commands: list[Command]
hidden: list[Command]
field
icon_action
list
def set_icon(self, icon: PySide6.QtGui.QIcon):
247    def set_icon(self, icon: QIcon):
248        """The icon in front of the field (the menu item's, in the theme's colors)."""
249        self.icon_action.setIcon(icon)

The icon in front of the field (the menu item's, in the theme's colors).

def open(self):
251    def open(self):
252        """Read the menus, and show the palette at the top of the window."""
253        menu_bar = self.window_.menuBar() if hasattr(self.window_, "menuBar") else None
254        self.commands = collect(menu_bar) if menu_bar is not None else []
255        self.field.clear()
256        self._filter("")
257        width = min(self.WIDTH, max(300, self.window_.width() - 40))
258        row = self.list.sizeHintForRow(0) if self.list.count() else 24
259        self.resize(width, self.field.sizeHint().height() + row * self.ROWS + 24)
260        top = self.window_.mapToGlobal(self.window_.rect().topLeft())
261        self.move(top.x() + (self.window_.width() - width) // 2, top.y() + 40)
262        self.show()
263        self.raise_()
264        self.field.setFocus()

Read the menus, and show the palette at the top of the window.

def eventFilter(self, obj, event):
280    def eventFilter(self, obj, event):
281        if obj is self.field and event.type() == QEvent.KeyPress:
282            key = event.key()
283            steps = {Qt.Key_Up: -1, Qt.Key_Down: 1, Qt.Key_PageUp: -self.ROWS, Qt.Key_PageDown: self.ROWS}
284            if key in steps and self.list.count():
285                row = max(0, min(self.list.count() - 1, self.list.currentRow() + steps[key]))
286                self.list.setCurrentRow(row)
287                return True
288            if key in (Qt.Key_Return, Qt.Key_Enter):
289                item = self.list.currentItem()
290                if item is not None:
291                    self.run(item.data(COMMAND_ROLE))
292                return True
293            if key == Qt.Key_Escape:
294                self.close()
295                return True
296        return super().eventFilter(obj, event)

eventFilter(self, watched: PySide6.QtCore.QObject, event: PySide6.QtCore.QEvent, /) -> bool

def run(self, command: Command):
298    def run(self, command: Command):
299        """Run a command (if it's available now): the palette closes first, so
300        the command acts where the focus was (e.g. the editor)."""
301        if not command.action.isEnabled():
302            return
303        title = command.title
304        if title in self.recent:
305            self.recent.remove(title)
306        self.recent.insert(0, title)
307        del self.recent[MAX_RECENT:]
308        if self.recent_changed is not None:
309            self.recent_changed(self.recent)
310        self.close()
311        QTimer.singleShot(0, self.window_, command.action.trigger)

Run a command (if it's available now): the palette closes first, so the command acts where the focus was (e.g. the editor).

def current_titles(self) -> list[str]:
313    def current_titles(self) -> list[str]:
314        """(The titles listed, in order: for the tests.)"""
315        return [self.list.item(i).data(COMMAND_ROLE).title for i in range(self.list.count())]

(The titles listed, in order: for the tests.)

staticMetaObject = PySide6.QtCore.QMetaObject("CommandPalette" inherits "QFrame": )