mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-11 07:52:50 -05:00
rename backend to core
This commit is contained in:
367
core/internal/sway/keybinds.go
Normal file
367
core/internal/sway/keybinds.go
Normal file
@@ -0,0 +1,367 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
TitleRegex = "#+!"
|
||||
HideComment = "[hidden]"
|
||||
)
|
||||
|
||||
var ModSeparators = []rune{'+', ' '}
|
||||
|
||||
type KeyBinding struct {
|
||||
Mods []string `json:"mods"`
|
||||
Key string `json:"key"`
|
||||
Command string `json:"command"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Children []Section `json:"children"`
|
||||
Keybinds []KeyBinding `json:"keybinds"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
contentLines []string
|
||||
readingLine int
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
contentLines: []string{},
|
||||
readingLine: 0,
|
||||
variables: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ReadContent(path string) error {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
expandedPath = filepath.Clean(expandedPath)
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
||||
}
|
||||
|
||||
info, err := os.Stat(expandedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var files []string
|
||||
if info.IsDir() {
|
||||
mainConfig := filepath.Join(expandedPath, "config")
|
||||
if fileInfo, err := os.Stat(mainConfig); err == nil && fileInfo.Mode().IsRegular() {
|
||||
files = []string{mainConfig}
|
||||
} else {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
} else {
|
||||
files = []string{expandedPath}
|
||||
}
|
||||
|
||||
var combinedContent []string
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
combinedContent = append(combinedContent, string(data))
|
||||
}
|
||||
|
||||
if len(combinedContent) == 0 {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
fullContent := strings.Join(combinedContent, "\n")
|
||||
p.contentLines = strings.Split(fullContent, "\n")
|
||||
p.parseVariables()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseVariables() {
|
||||
setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
|
||||
for _, line := range p.contentLines {
|
||||
matches := setRegex.FindStringSubmatch(line)
|
||||
if len(matches) == 3 {
|
||||
varName := matches[1]
|
||||
varValue := strings.TrimSpace(matches[2])
|
||||
p.variables[varName] = varValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) expandVariables(text string) string {
|
||||
result := text
|
||||
for varName, varValue := range p.variables {
|
||||
result = strings.ReplaceAll(result, "$"+varName, varValue)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func autogenerateComment(command string) string {
|
||||
command = strings.TrimSpace(command)
|
||||
|
||||
if strings.HasPrefix(command, "exec ") {
|
||||
cmdPart := strings.TrimPrefix(command, "exec ")
|
||||
cmdPart = strings.TrimPrefix(cmdPart, "--no-startup-id ")
|
||||
return cmdPart
|
||||
}
|
||||
|
||||
switch {
|
||||
case command == "kill":
|
||||
return "Close window"
|
||||
case command == "exit":
|
||||
return "Exit Sway"
|
||||
case command == "reload":
|
||||
return "Reload configuration"
|
||||
case strings.HasPrefix(command, "fullscreen"):
|
||||
return "Toggle fullscreen"
|
||||
case strings.HasPrefix(command, "floating toggle"):
|
||||
return "Float/unfloat window"
|
||||
case strings.HasPrefix(command, "focus mode_toggle"):
|
||||
return "Toggle focus mode"
|
||||
case strings.HasPrefix(command, "focus parent"):
|
||||
return "Focus parent container"
|
||||
case strings.HasPrefix(command, "focus left"):
|
||||
return "Focus left"
|
||||
case strings.HasPrefix(command, "focus right"):
|
||||
return "Focus right"
|
||||
case strings.HasPrefix(command, "focus up"):
|
||||
return "Focus up"
|
||||
case strings.HasPrefix(command, "focus down"):
|
||||
return "Focus down"
|
||||
case strings.HasPrefix(command, "focus output"):
|
||||
return "Focus monitor"
|
||||
case strings.HasPrefix(command, "move left"):
|
||||
return "Move window left"
|
||||
case strings.HasPrefix(command, "move right"):
|
||||
return "Move window right"
|
||||
case strings.HasPrefix(command, "move up"):
|
||||
return "Move window up"
|
||||
case strings.HasPrefix(command, "move down"):
|
||||
return "Move window down"
|
||||
case strings.HasPrefix(command, "move container to workspace"):
|
||||
if strings.Contains(command, "prev") {
|
||||
return "Move to previous workspace"
|
||||
}
|
||||
if strings.Contains(command, "next") {
|
||||
return "Move to next workspace"
|
||||
}
|
||||
parts := strings.Fields(command)
|
||||
if len(parts) > 4 {
|
||||
return "Move to workspace " + parts[len(parts)-1]
|
||||
}
|
||||
return "Move to workspace"
|
||||
case strings.HasPrefix(command, "move workspace to output"):
|
||||
return "Move workspace to monitor"
|
||||
case strings.HasPrefix(command, "workspace"):
|
||||
if strings.Contains(command, "prev") {
|
||||
return "Previous workspace"
|
||||
}
|
||||
if strings.Contains(command, "next") {
|
||||
return "Next workspace"
|
||||
}
|
||||
parts := strings.Fields(command)
|
||||
if len(parts) > 1 {
|
||||
wsNum := parts[len(parts)-1]
|
||||
return "Workspace " + wsNum
|
||||
}
|
||||
return "Switch workspace"
|
||||
case strings.HasPrefix(command, "layout"):
|
||||
parts := strings.Fields(command)
|
||||
if len(parts) > 1 {
|
||||
return "Layout " + parts[1]
|
||||
}
|
||||
return "Change layout"
|
||||
case strings.HasPrefix(command, "split"):
|
||||
if strings.Contains(command, "h") {
|
||||
return "Split horizontal"
|
||||
}
|
||||
if strings.Contains(command, "v") {
|
||||
return "Split vertical"
|
||||
}
|
||||
return "Split container"
|
||||
case strings.HasPrefix(command, "resize"):
|
||||
return "Resize window"
|
||||
case strings.Contains(command, "scratchpad"):
|
||||
return "Toggle scratchpad"
|
||||
default:
|
||||
return command
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
if lineNumber >= len(p.contentLines) {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := p.contentLines[lineNumber]
|
||||
|
||||
bindMatch := regexp.MustCompile(`^\s*(bindsym|bindcode)\s+(.+)$`)
|
||||
matches := bindMatch.FindStringSubmatch(line)
|
||||
if len(matches) < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
content := matches[2]
|
||||
|
||||
parts := strings.SplitN(content, "#", 2)
|
||||
keys := strings.TrimSpace(parts[0])
|
||||
|
||||
var comment string
|
||||
if len(parts) > 1 {
|
||||
comment = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
if strings.HasPrefix(comment, HideComment) {
|
||||
return nil
|
||||
}
|
||||
|
||||
flags := ""
|
||||
if strings.HasPrefix(keys, "--") {
|
||||
spaceIdx := strings.Index(keys, " ")
|
||||
if spaceIdx > 0 {
|
||||
flags = keys[:spaceIdx]
|
||||
keys = strings.TrimSpace(keys[spaceIdx+1:])
|
||||
}
|
||||
}
|
||||
|
||||
keyParts := strings.Fields(keys)
|
||||
if len(keyParts) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
keyCombo := keyParts[0]
|
||||
keyCombo = p.expandVariables(keyCombo)
|
||||
command := strings.Join(keyParts[1:], " ")
|
||||
command = p.expandVariables(command)
|
||||
|
||||
var modList []string
|
||||
var key string
|
||||
|
||||
modstring := keyCombo + string(ModSeparators[0])
|
||||
pos := 0
|
||||
for index, char := range modstring {
|
||||
isModSep := false
|
||||
for _, sep := range ModSeparators {
|
||||
if char == sep {
|
||||
isModSep = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isModSep {
|
||||
if index-pos > 0 {
|
||||
part := modstring[pos:index]
|
||||
if isMod(part) {
|
||||
modList = append(modList, part)
|
||||
} else {
|
||||
key = part
|
||||
}
|
||||
}
|
||||
pos = index + 1
|
||||
}
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
comment = autogenerateComment(command)
|
||||
}
|
||||
|
||||
_ = flags
|
||||
|
||||
return &KeyBinding{
|
||||
Mods: modList,
|
||||
Key: key,
|
||||
Command: command,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
func isMod(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
|
||||
s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
|
||||
strings.HasPrefix(s, "$") {
|
||||
return true
|
||||
}
|
||||
|
||||
isNumeric := true
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
isNumeric = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isNumeric && len(s) >= 2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section {
|
||||
titleRegex := regexp.MustCompile(TitleRegex)
|
||||
|
||||
for p.readingLine < len(p.contentLines) {
|
||||
line := p.contentLines[p.readingLine]
|
||||
|
||||
loc := titleRegex.FindStringIndex(line)
|
||||
if loc != nil && loc[0] == 0 {
|
||||
headingScope := strings.Index(line, "!")
|
||||
|
||||
if headingScope <= scope {
|
||||
p.readingLine--
|
||||
return currentContent
|
||||
}
|
||||
|
||||
sectionName := strings.TrimSpace(line[headingScope+1:])
|
||||
p.readingLine++
|
||||
|
||||
childSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
Name: sectionName,
|
||||
}
|
||||
result := p.getBindsRecursive(childSection, headingScope)
|
||||
currentContent.Children = append(currentContent.Children, *result)
|
||||
|
||||
} else if line == "" || (!strings.Contains(line, "bindsym") && !strings.Contains(line, "bindcode")) {
|
||||
|
||||
} else {
|
||||
keybind := p.getKeybindAtLine(p.readingLine)
|
||||
if keybind != nil {
|
||||
currentContent.Keybinds = append(currentContent.Keybinds, *keybind)
|
||||
}
|
||||
}
|
||||
|
||||
p.readingLine++
|
||||
}
|
||||
|
||||
return currentContent
|
||||
}
|
||||
|
||||
func (p *Parser) ParseKeys() *Section {
|
||||
p.readingLine = 0
|
||||
rootSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
Name: "",
|
||||
}
|
||||
return p.getBindsRecursive(rootSection, 0)
|
||||
}
|
||||
|
||||
func ParseKeys(path string) (*Section, error) {
|
||||
parser := NewParser()
|
||||
if err := parser.ReadContent(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parser.ParseKeys(), nil
|
||||
}
|
||||
471
core/internal/sway/keybinds_test.go
Normal file
471
core/internal/sway/keybinds_test.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package sway
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutogenerateComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
command string
|
||||
expected string
|
||||
}{
|
||||
{"exec kitty", "kitty"},
|
||||
{"exec --no-startup-id firefox", "firefox"},
|
||||
{"kill", "Close window"},
|
||||
{"exit", "Exit Sway"},
|
||||
{"reload", "Reload configuration"},
|
||||
{"fullscreen toggle", "Toggle fullscreen"},
|
||||
{"floating toggle", "Float/unfloat window"},
|
||||
{"focus mode_toggle", "Toggle focus mode"},
|
||||
{"focus parent", "Focus parent container"},
|
||||
{"focus left", "Focus left"},
|
||||
{"focus right", "Focus right"},
|
||||
{"focus up", "Focus up"},
|
||||
{"focus down", "Focus down"},
|
||||
{"focus output left", "Focus monitor"},
|
||||
{"move left", "Move window left"},
|
||||
{"move right", "Move window right"},
|
||||
{"move up", "Move window up"},
|
||||
{"move down", "Move window down"},
|
||||
{"move container to workspace number 1", "Move to workspace 1"},
|
||||
{"move container to workspace prev", "Move to previous workspace"},
|
||||
{"move container to workspace next", "Move to next workspace"},
|
||||
{"move workspace to output left", "Move workspace to monitor"},
|
||||
{"workspace number 1", "Workspace 1"},
|
||||
{"workspace prev", "Previous workspace"},
|
||||
{"workspace next", "Next workspace"},
|
||||
{"layout tabbed", "Layout tabbed"},
|
||||
{"layout stacking", "Layout stacking"},
|
||||
{"splith", "Split horizontal"},
|
||||
{"splitv", "Split vertical"},
|
||||
{"resize grow width 10 ppt", "Resize window"},
|
||||
{"move scratchpad", "Toggle scratchpad"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.command, func(t *testing.T) {
|
||||
result := autogenerateComment(tt.command)
|
||||
if result != tt.expected {
|
||||
t.Errorf("autogenerateComment(%q) = %q, want %q",
|
||||
tt.command, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKeybindAtLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected *KeyBinding
|
||||
}{
|
||||
{
|
||||
name: "basic_keybind",
|
||||
line: "bindsym Mod4+q kill",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "q",
|
||||
Command: "kill",
|
||||
Comment: "Close window",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_with_exec",
|
||||
line: "bindsym Mod4+t exec kitty",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "t",
|
||||
Command: "exec kitty",
|
||||
Comment: "kitty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_with_comment",
|
||||
line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "Space",
|
||||
Command: "exec dms ipc call spotlight toggle",
|
||||
Comment: "Open launcher",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_hidden",
|
||||
line: "bindsym Mod4+h exec secret # [hidden]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "keybind_multiple_mods",
|
||||
line: "bindsym Mod4+Shift+e exit",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4", "Shift"},
|
||||
Key: "e",
|
||||
Command: "exit",
|
||||
Comment: "Exit Sway",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_no_mods",
|
||||
line: "bindsym Print exec grim screenshot.png",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
Command: "exec grim screenshot.png",
|
||||
Comment: "grim screenshot.png",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_with_flags",
|
||||
line: "bindsym --release Mod4+x exec notify-send released",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "x",
|
||||
Command: "exec notify-send released",
|
||||
Comment: "notify-send released",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_focus_direction",
|
||||
line: "bindsym Mod4+Left focus left",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "Left",
|
||||
Command: "focus left",
|
||||
Comment: "Focus left",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keybind_workspace",
|
||||
line: "bindsym Mod4+1 workspace number 1",
|
||||
expected: &KeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "1",
|
||||
Command: "workspace number 1",
|
||||
Comment: "Workspace 1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.contentLines = []string{tt.line}
|
||||
result := parser.getKeybindAtLine(0)
|
||||
|
||||
if tt.expected == nil {
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %+v", result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Errorf("expected %+v, got nil", tt.expected)
|
||||
return
|
||||
}
|
||||
|
||||
if result.Key != tt.expected.Key {
|
||||
t.Errorf("Key = %q, want %q", result.Key, tt.expected.Key)
|
||||
}
|
||||
if result.Command != tt.expected.Command {
|
||||
t.Errorf("Command = %q, want %q", result.Command, tt.expected.Command)
|
||||
}
|
||||
if result.Comment != tt.expected.Comment {
|
||||
t.Errorf("Comment = %q, want %q", result.Comment, tt.expected.Comment)
|
||||
}
|
||||
if len(result.Mods) != len(tt.expected.Mods) {
|
||||
t.Errorf("Mods length = %d, want %d", len(result.Mods), len(tt.expected.Mods))
|
||||
} else {
|
||||
for i := range result.Mods {
|
||||
if result.Mods[i] != tt.expected.Mods[i] {
|
||||
t.Errorf("Mods[%d] = %q, want %q", i, result.Mods[i], tt.expected.Mods[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableExpansion(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
content := `set $mod Mod4
|
||||
set $term kitty
|
||||
set $menu rofi
|
||||
|
||||
bindsym $mod+t exec $term
|
||||
bindsym $mod+d exec $menu
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(section.Keybinds) != 2 {
|
||||
t.Errorf("Expected 2 keybinds, got %d", len(section.Keybinds))
|
||||
}
|
||||
|
||||
if len(section.Keybinds) > 0 {
|
||||
if section.Keybinds[0].Mods[0] != "Mod4" {
|
||||
t.Errorf("Expected Mod4, got %q", section.Keybinds[0].Mods[0])
|
||||
}
|
||||
if section.Keybinds[0].Command != "exec kitty" {
|
||||
t.Errorf("Expected 'exec kitty', got %q", section.Keybinds[0].Command)
|
||||
}
|
||||
}
|
||||
|
||||
if len(section.Keybinds) > 1 {
|
||||
if section.Keybinds[1].Command != "exec rofi" {
|
||||
t.Errorf("Expected 'exec rofi', got %q", section.Keybinds[1].Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeysWithSections(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
content := `set $mod Mod4
|
||||
|
||||
##! Window Management
|
||||
bindsym $mod+q kill
|
||||
bindsym $mod+f fullscreen toggle
|
||||
|
||||
###! Focus
|
||||
bindsym $mod+Left focus left
|
||||
bindsym $mod+Right focus right
|
||||
|
||||
##! Applications
|
||||
bindsym $mod+t exec kitty # Terminal
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(section.Children) != 2 {
|
||||
t.Errorf("Expected 2 top-level sections, got %d", len(section.Children))
|
||||
}
|
||||
|
||||
if len(section.Children) >= 1 {
|
||||
windowMgmt := section.Children[0]
|
||||
if windowMgmt.Name != "Window Management" {
|
||||
t.Errorf("First section name = %q, want %q", windowMgmt.Name, "Window Management")
|
||||
}
|
||||
if len(windowMgmt.Keybinds) != 2 {
|
||||
t.Errorf("Window Management keybinds = %d, want 2", len(windowMgmt.Keybinds))
|
||||
}
|
||||
|
||||
if len(windowMgmt.Children) != 1 {
|
||||
t.Errorf("Window Management children = %d, want 1", len(windowMgmt.Children))
|
||||
} else {
|
||||
focus := windowMgmt.Children[0]
|
||||
if focus.Name != "Focus" {
|
||||
t.Errorf("Focus section name = %q, want %q", focus.Name, "Focus")
|
||||
}
|
||||
if len(focus.Keybinds) != 2 {
|
||||
t.Errorf("Focus keybinds = %d, want 2", len(focus.Keybinds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(section.Children) >= 2 {
|
||||
apps := section.Children[1]
|
||||
if apps.Name != "Applications" {
|
||||
t.Errorf("Second section name = %q, want %q", apps.Name, "Applications")
|
||||
}
|
||||
if len(apps.Keybinds) != 1 {
|
||||
t.Errorf("Applications keybinds = %d, want 1", len(apps.Keybinds))
|
||||
}
|
||||
if len(apps.Keybinds) > 0 && apps.Keybinds[0].Comment != "Terminal" {
|
||||
t.Errorf("Applications keybind comment = %q, want %q", apps.Keybinds[0].Comment, "Terminal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadContentErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{
|
||||
name: "nonexistent_directory",
|
||||
path: "/nonexistent/path/that/does/not/exist",
|
||||
},
|
||||
{
|
||||
name: "empty_directory",
|
||||
path: t.TempDir(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseKeys(tt.path)
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadContentWithTildeExpansion(t *testing.T) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Skip("Cannot get home directory")
|
||||
}
|
||||
|
||||
tmpSubdir := filepath.Join(homeDir, ".config", "test-sway-"+t.Name())
|
||||
if err := os.MkdirAll(tmpSubdir, 0755); err != nil {
|
||||
t.Skip("Cannot create test directory in home")
|
||||
}
|
||||
defer os.RemoveAll(tmpSubdir)
|
||||
|
||||
configFile := filepath.Join(tmpSubdir, "config")
|
||||
if err := os.WriteFile(configFile, []byte("bindsym Mod4+q kill\n"), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(homeDir, tmpSubdir)
|
||||
if err != nil {
|
||||
t.Skip("Cannot create relative path")
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
tildePathMatch := "~/" + relPath
|
||||
err = parser.ReadContent(tildePathMatch)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("ReadContent with tilde path failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyAndCommentLines(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
content := `
|
||||
# This is a comment
|
||||
bindsym Mod4+q kill
|
||||
|
||||
# Another comment
|
||||
|
||||
bindsym Mod4+t exec kitty
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(section.Keybinds) != 2 {
|
||||
t.Errorf("Expected 2 keybinds (comments ignored), got %d", len(section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealWorldConfig(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
content := `set $mod Mod4
|
||||
set $term kitty
|
||||
|
||||
## Application Launchers
|
||||
bindsym $mod+t exec $term
|
||||
bindsym $mod+Space exec rofi
|
||||
|
||||
## Window Management
|
||||
bindsym $mod+q kill
|
||||
bindsym $mod+f fullscreen toggle
|
||||
|
||||
## Focus Navigation
|
||||
bindsym $mod+Left focus left
|
||||
bindsym $mod+Right focus right
|
||||
|
||||
## Workspace Navigation
|
||||
bindsym $mod+1 workspace number 1
|
||||
bindsym $mod+2 workspace number 2
|
||||
bindsym $mod+Shift+1 move container to workspace number 1
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(section.Keybinds) < 9 {
|
||||
t.Errorf("Expected at least 9 keybinds, got %d", len(section.Keybinds))
|
||||
}
|
||||
|
||||
foundExec := false
|
||||
foundKill := false
|
||||
foundWorkspace := false
|
||||
|
||||
for _, kb := range section.Keybinds {
|
||||
if kb.Command == "exec kitty" {
|
||||
foundExec = true
|
||||
}
|
||||
if kb.Command == "kill" {
|
||||
foundKill = true
|
||||
}
|
||||
if kb.Command == "workspace number 1" {
|
||||
foundWorkspace = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundExec {
|
||||
t.Error("Did not find exec kitty keybind")
|
||||
}
|
||||
if !foundKill {
|
||||
t.Error("Did not find kill keybind")
|
||||
}
|
||||
if !foundWorkspace {
|
||||
t.Error("Did not find workspace 1 keybind")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMod(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"Mod4", true},
|
||||
{"Shift", true},
|
||||
{"Control", true},
|
||||
{"Alt", true},
|
||||
{"Super", true},
|
||||
{"$mod", true},
|
||||
{"Left", false},
|
||||
{"q", false},
|
||||
{"1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
result := isMod(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("isMod(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user