mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -224,6 +225,44 @@ func (h *HyprlandProvider) validateAction(action string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var luaExprActionPattern = regexp.MustCompile(`^(function\s*\(|hl\.)`)
|
||||||
|
|
||||||
|
// isRawLuaActionText reports that action is a Lua expression to re-emit
|
||||||
|
// verbatim rather than freeform dispatcher text to wrap for hyprctl. The
|
||||||
|
// balance check keeps malformed input from corrupting the generated file.
|
||||||
|
func isRawLuaActionText(action string) bool {
|
||||||
|
if !luaExprActionPattern.MatchString(action) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
depth := 0
|
||||||
|
var quote byte
|
||||||
|
escaped := false
|
||||||
|
for i := 0; i < len(action); i++ {
|
||||||
|
c := action[i]
|
||||||
|
switch {
|
||||||
|
case escaped:
|
||||||
|
escaped = false
|
||||||
|
case quote != 0:
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
escaped = true
|
||||||
|
case quote:
|
||||||
|
quote = 0
|
||||||
|
}
|
||||||
|
case c == '"' || c == '\'':
|
||||||
|
quote = c
|
||||||
|
case c == '(':
|
||||||
|
depth++
|
||||||
|
case c == ')':
|
||||||
|
depth--
|
||||||
|
if depth < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return depth == 0 && quote == 0 && !escaped
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HyprlandProvider) SetBind(key, action, description string, options map[string]any) error {
|
func (h *HyprlandProvider) SetBind(key, action, description string, options map[string]any) error {
|
||||||
if err := h.ensureWritableConfig(); err != nil {
|
if err := h.ensureWritableConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -254,11 +293,12 @@ func (h *HyprlandProvider) SetBind(key, action, description string, options map[
|
|||||||
canonicalKey := canonicalHyprlandOverrideKey(key)
|
canonicalKey := canonicalHyprlandOverrideKey(key)
|
||||||
normalizedKey := hyprlandOverrideMapKey(canonicalKey)
|
normalizedKey := hyprlandOverrideMapKey(canonicalKey)
|
||||||
existingBinds[normalizedKey] = &hyprlandOverrideBind{
|
existingBinds[normalizedKey] = &hyprlandOverrideBind{
|
||||||
Key: canonicalKey,
|
Key: canonicalKey,
|
||||||
Action: action,
|
Action: action,
|
||||||
Description: description,
|
Description: description,
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
RawLuaAction: isRawLuaActionText(action),
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.writeOverrideBinds(existingBinds)
|
return h.writeOverrideBinds(existingBinds)
|
||||||
|
|||||||
@@ -463,6 +463,56 @@ func TestHyprlandSetBindLeavesConfOnlyInstallReadOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsRawLuaActionText(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
action string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{`function() hl.plugin.scrolloverview.overview("toggle") end`, true},
|
||||||
|
{`hl.dsp.exec_cmd("foo")`, true},
|
||||||
|
{`hl.dsp.no_op()`, true},
|
||||||
|
{"workspace 3", false},
|
||||||
|
{"exec zeditor", false},
|
||||||
|
{`function() hl.foo( end`, false},
|
||||||
|
{`function() hl.foo("a) end`, false},
|
||||||
|
{`hl.foo()) hl.bar((`, false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got := isRawLuaActionText(tc.action); got != tc.want {
|
||||||
|
t.Errorf("isRawLuaActionText(%q) = %v, want %v", tc.action, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHyprlandSetBindPreservesRawLuaAction(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
dmsDir := filepath.Join(tmpDir, "dms")
|
||||||
|
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dmsDir, "binds-user.lua"), []byte("-- DMS user keybind overrides\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewHyprlandProvider(tmpDir)
|
||||||
|
rawAction := `function() hl.plugin.scrolloverview.overview("toggle") end`
|
||||||
|
if err := provider.SetBind("SUPER + G", rawAction, "Toggle overview", nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filepath.Join(dmsDir, "binds-user.lua"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := string(data)
|
||||||
|
if !strings.Contains(got, `hl.bind("SUPER + G", `+rawAction) {
|
||||||
|
t.Fatalf("expected raw Lua action to be written verbatim, got:\n%s", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "hyprctl dispatch function") {
|
||||||
|
t.Fatalf("expected raw Lua action to not be wrapped in hyprctl dispatch, got:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHyprlandSetBindUpdatesSpacedLuaOverrideWithoutDuplicates(t *testing.T) {
|
func TestHyprlandSetBindUpdatesSpacedLuaOverrideWithoutDuplicates(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dmsDir := filepath.Join(tmpDir, "dms")
|
dmsDir := filepath.Join(tmpDir, "dms")
|
||||||
|
|||||||
Reference in New Issue
Block a user