Add commands and key bindings

This commit is contained in:
EnumDev 2025-06-09 18:31:07 +03:00
parent c6c7bddb2a
commit 2b0f190f15
5 changed files with 428 additions and 133 deletions

271
src/command.go Normal file
View File

@ -0,0 +1,271 @@
package main
import (
"fmt"
"log"
"maps"
"slices"
"strings"
)
type Command struct {
cmd string
run func(window *Window, args ...string)
autocomplete func(window *Window, args ...string) []string
}
var commands = make(map[string]*Command)
func initCommands() {
// Setup commands
copyCmd := Command{
cmd: "copy",
run: func(window *Window, args ...string) {
if window.CurrentBuffer.Selection == nil {
// Copy line
_, line := window.GetCursorPos2D()
window.Clipboard = strings.SplitAfter(window.CurrentBuffer.Contents, "\n")[line]
PrintMessage(window, "Copied line to clipboard.")
} else {
// Copy selection
window.Clipboard = window.CurrentBuffer.GetSelectedText()
PrintMessage(window, "Copied selection to clipboard.")
}
},
}
pasteCmd := Command{
cmd: "paste",
run: func(window *Window, args ...string) {
str := window.CurrentBuffer.Contents
index := window.CurrentBuffer.CursorPos
if index == len(str) {
str += window.Clipboard
} else {
str = str[:index] + window.Clipboard + str[index:]
}
window.CurrentBuffer.CursorPos += len(window.Clipboard)
window.CurrentBuffer.Contents = str
},
}
saveCmd := Command{
cmd: "save",
run: func(window *Window, args ...string) {
if !window.CurrentBuffer.canSave {
PrintMessage(window, "Cannot save buffer!")
return
}
inputChannel := RequestInput(window, "Save file [y\\N]:", "")
go func() {
input := <-inputChannel
if strings.ToLower(input) != "y" && strings.ToLower(input) != "yes" {
return
}
inputChannel = RequestInput(window, "Save buffer to:", window.CurrentBuffer.filename)
input = <-inputChannel
if strings.TrimSpace(input) == "" {
PrintMessage(window, "No save location was given!")
return
}
window.CurrentBuffer.filename = strings.TrimSpace(input)
err := window.CurrentBuffer.Save()
if err != nil {
PrintMessage(window, fmt.Sprintf("Could not save file: %s", err))
window.CurrentBuffer.filename = ""
return
}
PrintMessage(window, "File saved.")
}()
},
autocomplete: func(window *Window, args ...string) []string {
return nil
},
}
openCmd := Command{
cmd: "open",
run: func(window *Window, args ...string) {
inputChannel := RequestInput(window, "File to open:", "")
go func() {
input := <-inputChannel
if input == "" {
return
}
if openBuffer := GetOpenFileBuffer(input); openBuffer != nil {
PrintMessage(window, fmt.Sprintf("File already open! Switching to buffer: %s", openBuffer.Name))
window.CurrentBuffer = openBuffer
} else {
newBuffer, err := CreateFileBuffer(input, false)
if err != nil {
PrintMessage(window, fmt.Sprintf("Could not open file: %s", err.Error()))
return
}
PrintMessage(window, fmt.Sprintf("Opening file at: %s", newBuffer.filename))
window.CurrentBuffer = newBuffer
}
}()
},
}
reloadCmd := Command{
cmd: "reload",
run: func(window *Window, args ...string) {
err := window.CurrentBuffer.Load()
if err != nil {
log.Fatalf("Could not reload buffer: %s", err)
}
window.SetCursorPos(window.CurrentBuffer.CursorPos)
PrintMessage(window, "Buffer reloaded.")
},
}
prevBufferCmd := Command{
cmd: "prev-buffer",
run: func(window *Window, args ...string) {
if window.CursorMode != CursorModeBuffer {
return
}
buffers := slices.Collect(maps.Values(Buffers))
index := slices.Index(buffers, window.CurrentBuffer)
index--
if index < 0 {
index = 0
}
window.CurrentBuffer = buffers[index]
},
}
nextBufferCmd := Command{
cmd: "next-buffer",
run: func(window *Window, args ...string) {
if window.CursorMode != CursorModeBuffer {
return
}
buffers := slices.Collect(maps.Values(Buffers))
index := slices.Index(buffers, window.CurrentBuffer)
index++
if index >= len(buffers) {
index = len(buffers) - 1
}
window.CurrentBuffer = buffers[index]
},
}
newBufferCmd := Command{
cmd: "new-buffer",
run: func(window *Window, args ...string) {
number := 1
for _, buffer := range Buffers {
if strings.HasPrefix(buffer.Name, "New File ") {
number++
}
}
buffer := CreateBuffer(fmt.Sprintf("New File %d", number))
window.CurrentBuffer = buffer
window.CursorMode = CursorModeBuffer
},
}
closeBufferCmd := Command{
cmd: "close-buffer",
run: func(window *Window, args ...string) {
delete(Buffers, window.CurrentBuffer.Id)
buffersSlice := slices.Collect(maps.Values(Buffers))
if len(buffersSlice) == 0 {
window.Close()
return
}
window.CurrentBuffer = buffersSlice[0]
window.CursorMode = CursorModeBuffer
},
}
menuFileCmd := Command{
cmd: "menu-file",
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "File" {
button.Action(window)
break
}
}
},
}
menuEditCmd := Command{
cmd: "menu-edit",
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "Edit" {
button.Action(window)
break
}
}
},
}
menuBuffersCmd := Command{
cmd: "menu-buffers",
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "Buffers" {
button.Action(window)
break
}
}
},
}
quitCmd := Command{
cmd: "quit",
run: func(window *Window, args ...string) {
window.Close()
window.CursorMode = CursorModeBuffer
},
}
// Register commands
commands["copy"] = &copyCmd
commands["paste"] = &pasteCmd
commands["save"] = &saveCmd
commands["open"] = &openCmd
commands["reload"] = &reloadCmd
commands["prev-buffer"] = &prevBufferCmd
commands["next-buffer"] = &nextBufferCmd
commands["new-buffer"] = &newBufferCmd
commands["close-buffer"] = &closeBufferCmd
commands["menu-file"] = &menuFileCmd
commands["menu-edit"] = &menuEditCmd
commands["menu-buffers"] = &menuBuffersCmd
commands["quit"] = &quitCmd
}
func RunCommand(window *Window, cmd string, args ...string) bool {
if command, ok := commands[cmd]; ok {
command.run(window, args...)
return true
} else {
PrintMessage(window, fmt.Sprintf("Could not find command '%s'!", cmd))
return false
}
}

133
src/keybindings.go Normal file
View File

@ -0,0 +1,133 @@
package main
import (
"github.com/gdamore/tcell/v2"
"strings"
)
type Keybinding struct {
keybind string
command string
}
var Keybinds = make([]Keybinding, 0)
func initKeybindings() {
// Add key bindings
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-Q",
command: "quit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-C",
command: "copy",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-V",
command: "paste",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-S",
command: "save",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-O",
command: "open",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-R",
command: "reload",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "PgUp",
command: "prev-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "PgDn",
command: "next-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-N",
command: "new-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Delete",
command: "close-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-Q",
command: "quit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F1",
command: "menu-file",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F2",
command: "menu-edit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F3",
command: "menu-buffers",
})
}
func (keybind *Keybinding) IsPressed(ev *tcell.EventKey) bool {
keys := strings.SplitN(keybind.keybind, "+", 2)
if len(keys) == 0 {
return false
} else if len(keys) == 1 {
for k, v := range tcell.KeyNames {
if k != tcell.KeyRune {
if keybind.keybind == v {
if ev.Key() == k {
return true
}
}
} else {
if keybind.keybind == string(ev.Rune()) {
return true
}
}
}
} else {
modKey := keys[0]
key := keys[1]
switch modKey {
case "Shift":
if ev.Modifiers() != tcell.ModShift {
return false
}
case "Alt":
if ev.Modifiers() != tcell.ModAlt {
return false
}
case "Ctrl":
if ev.Modifiers() != tcell.ModCtrl {
return false
}
case "Meta":
if ev.Modifiers() != tcell.ModMeta {
return false
}
}
for k, v := range tcell.KeyNames {
if k != tcell.KeyRune {
if key == v {
if ev.Key() == k {
return true
}
}
}
}
if strings.ToLower(key) == string(ev.Rune()) {
return true
}
}
return false
}

View File

@ -6,6 +6,12 @@ import (
)
func main() {
// Initialize commands
initCommands()
// Initialize key bindings
initKeybindings()
window, err := CreateWindow()
if err != nil {
log.Fatalf("Failed to create window: %v", err)

View File

@ -3,7 +3,6 @@ package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"maps"
"slices"
"strconv"
"strings"
@ -11,7 +10,6 @@ import (
type TopMenuButton struct {
Name string
Key rune
Action func(w *Window)
}
@ -21,89 +19,20 @@ func initTopMenu() {
// Buttons
fileButton := TopMenuButton{
Name: "File",
Key: 'f',
Action: func(window *Window) {
ClearDropdowns()
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, 0, 1, 0, func(i int) {
switch i {
case 0:
number := 1
for _, buffer := range Buffers {
if strings.HasPrefix(buffer.Name, "New File ") {
number++
}
}
buffer := CreateBuffer(fmt.Sprintf("New File %d", number))
window.CurrentBuffer = buffer
window.CursorMode = CursorModeBuffer
RunCommand(window, "new-buffer")
case 1:
if !window.CurrentBuffer.canSave {
PrintMessage(window, "Cannot save this buffer!")
return
}
inputChannel := RequestInput(window, "Save file [y\\N]:", "")
go func() {
input := <-inputChannel
if strings.ToLower(input) != "y" && strings.ToLower(input) != "yes" {
return
}
inputChannel = RequestInput(window, "Save buffer to:", window.CurrentBuffer.filename)
input = <-inputChannel
if strings.TrimSpace(input) == "" {
PrintMessage(window, "No save location was given!")
return
}
window.CurrentBuffer.filename = strings.TrimSpace(input)
err := window.CurrentBuffer.Save()
if err != nil {
PrintMessage(window, fmt.Sprintf("Could not save file: %s", err))
window.CurrentBuffer.filename = ""
return
}
PrintMessage(window, "File saved.")
}()
RunCommand(window, "save")
case 2:
inputChannel := RequestInput(window, "File to open:", "")
go func() {
input := <-inputChannel
if input == "" {
return
}
if openBuffer := GetOpenFileBuffer(input); openBuffer != nil {
PrintMessage(window, fmt.Sprintf("File already open! Switching to buffer: %s", openBuffer.Name))
window.CurrentBuffer = openBuffer
} else {
newBuffer, err := CreateFileBuffer(input, false)
if err != nil {
PrintMessage(window, fmt.Sprintf("Could not open file: %s", err.Error()))
return
}
PrintMessage(window, fmt.Sprintf("Opening file at: %s", newBuffer.filename))
window.CurrentBuffer = newBuffer
}
}()
RunCommand(window, "open")
case 3:
delete(Buffers, window.CurrentBuffer.Id)
buffersSlice := slices.Collect(maps.Values(Buffers))
if len(buffersSlice) == 0 {
window.Close()
return
}
window.CurrentBuffer = buffersSlice[0]
window.CursorMode = CursorModeBuffer
RunCommand(window, "close-buffer")
case 4:
window.Close()
window.CursorMode = CursorModeBuffer
RunCommand(window, "quit")
}
ClearDropdowns()
})
@ -113,33 +42,14 @@ func initTopMenu() {
}
EditButton := TopMenuButton{
Name: "Edit",
Key: 'e',
Action: func(window *Window) {
ClearDropdowns()
d := CreateDropdownMenu([]string{"Copy", "Paste"}, 0, 1, 0, func(i int) {
switch i {
case 0:
if window.CurrentBuffer.Selection == nil {
// Copy line
_, line := window.GetCursorPos2D()
window.Clipboard = strings.SplitAfter(window.CurrentBuffer.Contents, "\n")[line]
PrintMessage(window, "Copied line to clipboard.")
} else {
// Copy selection
window.Clipboard = window.CurrentBuffer.GetSelectedText()
PrintMessage(window, "Copied selection to clipboard.")
}
RunCommand(window, "copy")
case 1:
str := window.CurrentBuffer.Contents
index := window.CurrentBuffer.CursorPos
if index == len(str) {
str += window.Clipboard
} else {
str = str[:index] + window.Clipboard + str[index:]
}
window.CurrentBuffer.CursorPos += len(window.Clipboard)
window.CurrentBuffer.Contents = str
RunCommand(window, "paste")
}
ClearDropdowns()
window.CursorMode = CursorModeBuffer
@ -150,7 +60,6 @@ func initTopMenu() {
}
Buffers := TopMenuButton{
Name: "Buffers",
Key: 'b',
Action: func(window *Window) {
ClearDropdowns()
buffersSlice := make([]string, 0)

View File

@ -4,7 +4,6 @@ import (
"github.com/gdamore/tcell/v2"
"log"
"slices"
"strings"
)
type CursorMode uint8
@ -180,7 +179,6 @@ func (window *Window) Draw() {
func (window *Window) input(ev *tcell.EventKey) {
if ev.Key() == tcell.KeyRight { // Navigation Keys
if window.CursorMode == CursorModeBuffer {
// Add to selection
if ev.Modifiers() == tcell.ModShift {
@ -330,42 +328,20 @@ func (window *Window) input(ev *tcell.EventKey) {
ClearDropdowns()
window.CursorMode = CursorModeBuffer
}
} else if ev.Key() == tcell.KeyCtrlC { // Copy to clipboard key
if window.CursorMode == CursorModeBuffer {
if window.CurrentBuffer.Selection == nil {
// Copy line
_, line := window.GetCursorPos2D()
window.Clipboard = strings.SplitAfter(window.CurrentBuffer.Contents, "\n")[line]
PrintMessage(window, "Copied line to clipboard.")
} else {
// Copy selection
window.Clipboard = window.CurrentBuffer.GetSelectedText()
PrintMessage(window, "Copied selection to clipboard.")
}
}
} else if ev.Key() == tcell.KeyCtrlV { // Paste from clipboard
if window.CursorMode == CursorModeBuffer {
str := window.CurrentBuffer.Contents
index := window.CurrentBuffer.CursorPos
}
if index == len(str) {
str += window.Clipboard
} else {
str = str[:index] + window.Clipboard + str[index:]
}
window.CurrentBuffer.CursorPos += len(window.Clipboard)
window.CurrentBuffer.Contents = str
}
} else if ev.Key() == tcell.KeyCtrlQ { // Exit key
window.Close()
} else if ev.Modifiers()&tcell.ModAlt != 0 { // Menu Bar
for _, button := range TopMenuButtons {
if ev.Rune() == button.Key {
button.Action(window)
break
// Check key bindings
if window.CursorMode == CursorModeBuffer {
for _, keybinding := range Keybinds {
if keybinding.IsPressed(ev) {
RunCommand(window, keybinding.command)
return
}
}
} else if ev.Key() == tcell.KeyBackspace2 { // Typing
}
// Typing
if ev.Key() == tcell.KeyBackspace2 {
if window.CursorMode == CursorModeBuffer {
str := window.CurrentBuffer.Contents
index := window.CurrentBuffer.CursorPos