mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
hyprland: add keybinds cheatsheet
This commit is contained in:
15
DMSShell.qml
15
DMSShell.qml
@@ -473,12 +473,27 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: hyprKeybindsModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
HyprKeybindsModal {
|
||||
id: hyprKeybindsModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.hyprKeybindsModal = hyprKeybindsModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DMSShellIPC {
|
||||
powerMenuModalLoader: powerMenuModalLoader
|
||||
processListModalLoader: processListModalLoader
|
||||
controlCenterLoader: controlCenterLoader
|
||||
dankDashPopoutLoader: dankDashPopoutLoader
|
||||
notepadSlideoutVariants: notepadSlideoutVariants
|
||||
hyprKeybindsModalLoader: hyprKeybindsModalLoader
|
||||
}
|
||||
|
||||
Variants {
|
||||
|
||||
@@ -13,6 +13,7 @@ Item {
|
||||
required property var controlCenterLoader
|
||||
required property var dankDashPopoutLoader
|
||||
required property var notepadSlideoutVariants
|
||||
required property var hyprKeybindsModalLoader
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
@@ -306,4 +307,47 @@ Item {
|
||||
|
||||
target: "mpris"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function openBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprKeybindsModalLoader.active = true
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
root.hyprKeybindsModalLoader.item.open()
|
||||
return "HYPR_KEYBINDS_OPEN_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function closeBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
root.hyprKeybindsModalLoader.item.close()
|
||||
return "HYPR_KEYBINDS_CLOSE_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggleBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprKeybindsModalLoader.active = true
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
|
||||
root.hyprKeybindsModalLoader.item.close()
|
||||
} else {
|
||||
root.hyprKeybindsModalLoader.item.open()
|
||||
}
|
||||
return "HYPR_KEYBINDS_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "hypr"
|
||||
}
|
||||
}
|
||||
|
||||
266
Modals/HyprKeybindsModal.qml
Normal file
266
Modals/HyprKeybindsModal.qml
Normal file
@@ -0,0 +1,266 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
width: 1400
|
||||
height: 900
|
||||
onBackgroundClicked: close()
|
||||
|
||||
function categorizeKeybinds() {
|
||||
const categories = {
|
||||
"Workspace": [],
|
||||
"Window": [],
|
||||
"Monitor": [],
|
||||
"Execute": [],
|
||||
"System": [],
|
||||
"Other": []
|
||||
}
|
||||
|
||||
function addKeybind(keybind) {
|
||||
const dispatcher = keybind.dispatcher || ""
|
||||
if (dispatcher.includes("workspace")) {
|
||||
categories["Workspace"].push(keybind)
|
||||
} else if (dispatcher.includes("monitor")) {
|
||||
categories["Monitor"].push(keybind)
|
||||
} else if (dispatcher.includes("window") || dispatcher.includes("focus") || dispatcher.includes("move") || dispatcher.includes("swap") || dispatcher.includes("resize") || dispatcher === "killactive" || dispatcher === "fullscreen" || dispatcher === "togglefloating") {
|
||||
categories["Window"].push(keybind)
|
||||
} else if (dispatcher === "exec") {
|
||||
categories["Execute"].push(keybind)
|
||||
} else if (dispatcher === "exit" || dispatcher.includes("dpms")) {
|
||||
categories["System"].push(keybind)
|
||||
} else {
|
||||
categories["Other"].push(keybind)
|
||||
}
|
||||
}
|
||||
|
||||
const allKeybinds = HyprKeybindsService.keybinds.keybinds || []
|
||||
for (let i = 0; i < allKeybinds.length; i++) {
|
||||
addKeybind(allKeybinds[i])
|
||||
}
|
||||
|
||||
const children = HyprKeybindsService.keybinds.children || []
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
const childKeybinds = child.keybinds || []
|
||||
for (let j = 0; j < childKeybinds.length; j++) {
|
||||
addKeybind(childKeybinds[j])
|
||||
}
|
||||
}
|
||||
|
||||
categories["Workspace"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Window"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Monitor"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Execute"].sort((a, b) => {
|
||||
const modsA = a.mods || []
|
||||
const keyA = a.key || ""
|
||||
const bindA = [...modsA, keyA].join("+")
|
||||
|
||||
const modsB = b.mods || []
|
||||
const keyB = b.key || ""
|
||||
const bindB = [...modsB, keyB].join("+")
|
||||
|
||||
return bindA.localeCompare(bindB)
|
||||
})
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
DankFlickable {
|
||||
id: mainFlickable
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
contentWidth: rowLayout.implicitWidth
|
||||
contentHeight: rowLayout.implicitHeight
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: rowLayout
|
||||
spacing: Theme.spacingM
|
||||
|
||||
property var categories: root.categorizeKeybinds()
|
||||
property real columnWidth: (mainFlickable.width - spacing * 2) / 3
|
||||
|
||||
Column {
|
||||
width: rowLayout.columnWidth
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Window / Monitor"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Item { width: 1; height: Theme.spacingXS }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: [...(rowLayout.categories["Window"] || []), ...(rowLayout.categories["Monitor"] || [])]
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: Math.min(140, parent.width * 0.42)
|
||||
height: 22
|
||||
radius: 4
|
||||
opacity: 0.3
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: 2
|
||||
width: parent.width - 4
|
||||
text: {
|
||||
const mods = modelData.mods || []
|
||||
const key = modelData.key || ""
|
||||
const parts = [...mods, key]
|
||||
return parts.join("+")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
isMonospace: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - 150
|
||||
text: {
|
||||
const comment = modelData.comment || ""
|
||||
if (comment) return comment
|
||||
|
||||
const dispatcher = modelData.dispatcher || ""
|
||||
const params = modelData.params || ""
|
||||
return params ? `${dispatcher} ${params}` : dispatcher
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
opacity: 0.9
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ["Workspace", "Execute"]
|
||||
|
||||
Column {
|
||||
width: rowLayout.columnWidth
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Item { width: 1; height: Theme.spacingXS }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: rowLayout.categories[modelData] || []
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: Math.min(140, parent.width * 0.42)
|
||||
height: 22
|
||||
radius: 4
|
||||
opacity: 0.3
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: 2
|
||||
width: parent.width - 4
|
||||
text: {
|
||||
const mods = modelData.mods || []
|
||||
const key = modelData.key || ""
|
||||
const parts = [...mods, key]
|
||||
return parts.join("+")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
isMonospace: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - 150
|
||||
text: {
|
||||
const comment = modelData.comment || ""
|
||||
if (comment) return comment
|
||||
|
||||
const dispatcher = modelData.dispatcher || ""
|
||||
const params = modelData.params || ""
|
||||
return params ? `${dispatcher} ${params}` : dispatcher
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
opacity: 0.9
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Services/HyprKeybindsService.qml
Normal file
39
Services/HyprKeybindsService.qml
Normal file
@@ -0,0 +1,39 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtCore
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Services/", "")
|
||||
property string scriptPath: `${shellDir}/scripts/hyprland_keybinds.py`
|
||||
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
|
||||
readonly property string _configDir: Paths.strip(_configUrl)
|
||||
property string hyprConfigPath: `${_configDir}/hypr`
|
||||
property var keybinds: ({"children": [], "keybinds": []})
|
||||
|
||||
Process {
|
||||
id: getKeybinds
|
||||
running: true
|
||||
command: [root.scriptPath, "--path", root.hyprConfigPath]
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
try {
|
||||
root.keybinds = JSON.parse(data)
|
||||
} catch (e) {
|
||||
console.error("[HyprKeybindsService] Error parsing keybinds:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reload() {
|
||||
getKeybinds.running = true
|
||||
}
|
||||
}
|
||||
38
docs/IPC.md
38
docs/IPC.md
@@ -511,6 +511,26 @@ File browser controls for selecting wallpapers and profile images.
|
||||
- `profile` - Opens profile image file browser in Pictures directory
|
||||
- Both browsers support common image formats (jpg, jpeg, png, bmp, gif, webp)
|
||||
|
||||
### Target: `hypr`
|
||||
Hyprland keybinds cheatsheet modal control (Hyprland only).
|
||||
|
||||
**Functions:**
|
||||
- `openBinds` - Show Hyprland keybinds cheatsheet modal
|
||||
- Returns: Success/failure message
|
||||
- Note: Returns "HYPR_NOT_AVAILABLE" if not running Hyprland
|
||||
- `closeBinds` - Hide Hyprland keybinds cheatsheet modal
|
||||
- Returns: Success/failure message
|
||||
- Note: Returns "HYPR_NOT_AVAILABLE" if not running Hyprland
|
||||
- `toggleBinds` - Toggle Hyprland keybinds cheatsheet modal visibility
|
||||
- Returns: Success/failure message
|
||||
- Note: Returns "HYPR_NOT_AVAILABLE" if not running Hyprland
|
||||
|
||||
**Description:**
|
||||
Displays an auto-categorized cheatsheet of all Hyprland keybinds parsed from `~/.config/hypr`. Keybinds are organized into three columns:
|
||||
- **Window / Monitor** - Window and monitor management keybinds (sorted by dispatcher)
|
||||
- **Workspace** - Workspace switching and management (sorted by dispatcher)
|
||||
- **Execute** - Application launchers and commands (sorted by keybind)
|
||||
|
||||
### Modal Examples
|
||||
```bash
|
||||
# Open application launcher
|
||||
@@ -546,14 +566,19 @@ dms ipc call dash open weather
|
||||
# Open file browsers
|
||||
dms ipc call file browse wallpaper
|
||||
dms ipc call file browse profile
|
||||
|
||||
# Show Hyprland keybinds cheatsheet (Hyprland only)
|
||||
dms ipc call hypr toggleBinds
|
||||
dms ipc call hypr openBinds
|
||||
```
|
||||
|
||||
## Common Usage Patterns
|
||||
|
||||
### Keybinding Integration
|
||||
|
||||
These IPC commands are designed to be used with window manager keybindings. Example niri configuration:
|
||||
These IPC commands are designed to be used with window manager keybindings.
|
||||
|
||||
**Example niri configuration:**
|
||||
```kdl
|
||||
binds {
|
||||
Mod+Space { spawn "qs" "-c" "dms" "ipc" "call" "spotlight" "toggle"; }
|
||||
@@ -565,6 +590,17 @@ binds {
|
||||
}
|
||||
```
|
||||
|
||||
**Example Hyprland configuration:**
|
||||
```conf
|
||||
bind = SUPER, Space, exec, qs -c dms ipc call spotlight toggle
|
||||
bind = SUPER, V, exec, qs -c dms ipc call clipboard toggle
|
||||
bind = SUPER, P, exec, qs -c dms ipc call notepad toggle
|
||||
bind = SUPER, X, exec, qs -c dms ipc call powermenu toggle
|
||||
bind = SUPER, slash, exec, qs -c dms ipc call hypr toggleBinds
|
||||
bind = , XF86AudioRaiseVolume, exec, qs -c dms ipc call audio increment 3
|
||||
bind = , XF86MonBrightnessUp, exec, qs -c dms ipc call brightness increment 5 ""
|
||||
```
|
||||
|
||||
### Scripting and Automation
|
||||
|
||||
IPC commands can be used in scripts for automation:
|
||||
|
||||
237
scripts/hyprland_keybinds.py
Executable file
237
scripts/hyprland_keybinds.py
Executable file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Based on end-4 dots-hyprland get_keybinds.py
|
||||
# https://github.com/end-4/dots-hyprland/blob/main/.config/quickshell/ii/scripts/hyprland/get_keybinds.py
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import os
|
||||
import glob
|
||||
from os.path import expandvars as os_expandvars
|
||||
from typing import Dict, List
|
||||
|
||||
TITLE_REGEX = "#+!"
|
||||
HIDE_COMMENT = "[hidden]"
|
||||
MOD_SEPARATORS = ['+', ' ']
|
||||
COMMENT_BIND_PATTERN = "#/#"
|
||||
|
||||
parser = argparse.ArgumentParser(description='Hyprland keybind reader')
|
||||
parser.add_argument('--path', type=str, default="$HOME/.config/hypr", help='path to hyprland config directory')
|
||||
args = parser.parse_args()
|
||||
content_lines = []
|
||||
reading_line = 0
|
||||
|
||||
Variables: Dict[str, str] = {}
|
||||
|
||||
|
||||
class KeyBinding(dict):
|
||||
def __init__(self, mods, key, dispatcher, params, comment) -> None:
|
||||
self["mods"] = mods
|
||||
self["key"] = key
|
||||
self["dispatcher"] = dispatcher
|
||||
self["params"] = params
|
||||
self["comment"] = comment
|
||||
|
||||
class Section(dict):
|
||||
def __init__(self, children, keybinds, name) -> None:
|
||||
self["children"] = children
|
||||
self["keybinds"] = keybinds
|
||||
self["name"] = name
|
||||
|
||||
|
||||
def read_content(directory: str) -> str:
|
||||
expanded_dir = os.path.expanduser(os.path.expandvars(directory))
|
||||
if not os.path.isdir(expanded_dir):
|
||||
return "error"
|
||||
|
||||
conf_files = glob.glob(os.path.join(expanded_dir, "*.conf"))
|
||||
if not conf_files:
|
||||
return "error"
|
||||
|
||||
combined_content = []
|
||||
for conf_file in sorted(conf_files):
|
||||
if os.access(conf_file, os.R_OK):
|
||||
with open(conf_file, "r") as file:
|
||||
combined_content.append(file.read())
|
||||
|
||||
return "\n".join(combined_content) if combined_content else "error"
|
||||
|
||||
|
||||
def autogenerate_comment(dispatcher: str, params: str = "") -> str:
|
||||
match dispatcher:
|
||||
|
||||
case "resizewindow":
|
||||
return "Resize window"
|
||||
|
||||
case "movewindow":
|
||||
if(params == ""):
|
||||
return "Move window"
|
||||
else:
|
||||
return "Window: move in {} direction".format({
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null"))
|
||||
|
||||
case "pin":
|
||||
return "Window: pin (show on all workspaces)"
|
||||
|
||||
case "splitratio":
|
||||
return "Window split ratio {}".format(params)
|
||||
|
||||
case "togglefloating":
|
||||
return "Float/unfloat window"
|
||||
|
||||
case "resizeactive":
|
||||
return "Resize window by {}".format(params)
|
||||
|
||||
case "killactive":
|
||||
return "Close window"
|
||||
|
||||
case "fullscreen":
|
||||
return "Toggle {}".format(
|
||||
{
|
||||
"0": "fullscreen",
|
||||
"1": "maximization",
|
||||
"2": "fullscreen on Hyprland's side",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "fakefullscreen":
|
||||
return "Toggle fake fullscreen"
|
||||
|
||||
case "workspace":
|
||||
if params == "+1":
|
||||
return "Workspace: focus right"
|
||||
elif params == "-1":
|
||||
return "Workspace: focus left"
|
||||
return "Focus workspace {}".format(params)
|
||||
|
||||
case "movefocus":
|
||||
return "Window: move focus {}".format(
|
||||
{
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "swapwindow":
|
||||
return "Window: swap in {} direction".format(
|
||||
{
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "movetoworkspace":
|
||||
if params == "+1":
|
||||
return "Window: move to right workspace (non-silent)"
|
||||
elif params == "-1":
|
||||
return "Window: move to left workspace (non-silent)"
|
||||
return "Window: move to workspace {} (non-silent)".format(params)
|
||||
|
||||
case "movetoworkspacesilent":
|
||||
if params == "+1":
|
||||
return "Window: move to right workspace"
|
||||
elif params == "-1":
|
||||
return "Window: move to right workspace"
|
||||
return "Window: move to workspace {}".format(params)
|
||||
|
||||
case "togglespecialworkspace":
|
||||
return "Workspace: toggle special"
|
||||
|
||||
case "exec":
|
||||
return "Execute: {}".format(params)
|
||||
|
||||
case _:
|
||||
return ""
|
||||
|
||||
def get_keybind_at_line(line_number, line_start = 0):
|
||||
global content_lines
|
||||
line = content_lines[line_number]
|
||||
_, keys = line.split("=", 1)
|
||||
keys, *comment = keys.split("#", 1)
|
||||
|
||||
mods, key, dispatcher, *params = list(map(str.strip, keys.split(",", 4)))
|
||||
params = "".join(map(str.strip, params))
|
||||
|
||||
# Remove empty spaces
|
||||
comment = list(map(str.strip, comment))
|
||||
# Add comment if it exists, else generate it
|
||||
if comment:
|
||||
comment = comment[0]
|
||||
if comment.startswith("[hidden]"):
|
||||
return None
|
||||
else:
|
||||
comment = autogenerate_comment(dispatcher, params)
|
||||
|
||||
if mods:
|
||||
modstring = mods + MOD_SEPARATORS[0] # Add separator at end to ensure last mod is read
|
||||
mods = []
|
||||
p = 0
|
||||
for index, char in enumerate(modstring):
|
||||
if(char in MOD_SEPARATORS):
|
||||
if(index - p > 1):
|
||||
mods.append(modstring[p:index])
|
||||
p = index+1
|
||||
else:
|
||||
mods = []
|
||||
|
||||
return KeyBinding(mods, key, dispatcher, params, comment)
|
||||
|
||||
def get_binds_recursive(current_content, scope):
|
||||
global content_lines
|
||||
global reading_line
|
||||
# print("get_binds_recursive({0}, {1}) [@L{2}]".format(current_content, scope, reading_line + 1))
|
||||
while reading_line < len(content_lines): # TODO: Adjust condition
|
||||
line = content_lines[reading_line]
|
||||
heading_search_result = re.search(TITLE_REGEX, line)
|
||||
# print("Read line {0}: {1}\tisHeading: {2}".format(reading_line + 1, content_lines[reading_line], "[{0}, {1}, {2}]".format(heading_search_result.start(), heading_search_result.start() == 0, ((heading_search_result != None) and (heading_search_result.start() == 0))) if heading_search_result != None else "No"))
|
||||
if ((heading_search_result != None) and (heading_search_result.start() == 0)): # Found title
|
||||
# Determine scope
|
||||
heading_scope = line.find('!')
|
||||
# Lower? Return
|
||||
if(heading_scope <= scope):
|
||||
reading_line -= 1
|
||||
return current_content
|
||||
|
||||
section_name = line[(heading_scope+1):].strip()
|
||||
# print("[[ Found h{0} at line {1} ]] {2}".format(heading_scope, reading_line+1, content_lines[reading_line]))
|
||||
reading_line += 1
|
||||
current_content["children"].append(get_binds_recursive(Section([], [], section_name), heading_scope))
|
||||
|
||||
elif line.startswith(COMMENT_BIND_PATTERN):
|
||||
keybind = get_keybind_at_line(reading_line, line_start=len(COMMENT_BIND_PATTERN))
|
||||
if(keybind != None):
|
||||
current_content["keybinds"].append(keybind)
|
||||
|
||||
elif line == "" or not line.lstrip().startswith("bind"): # Comment, ignore
|
||||
pass
|
||||
|
||||
else: # Normal keybind
|
||||
keybind = get_keybind_at_line(reading_line)
|
||||
if(keybind != None):
|
||||
current_content["keybinds"].append(keybind)
|
||||
|
||||
reading_line += 1
|
||||
|
||||
return current_content;
|
||||
|
||||
def parse_keys(path: str) -> Dict[str, List[KeyBinding]]:
|
||||
global content_lines
|
||||
content_lines = read_content(path).splitlines()
|
||||
if content_lines[0] == "error":
|
||||
return "error"
|
||||
return get_binds_recursive(Section([], [], ""), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
ParsedKeys = parse_keys(args.path)
|
||||
print(json.dumps(ParsedKeys))
|
||||
Reference in New Issue
Block a user