mirror of
https://github.com/EnumeratedDev/Typer.git
synced 2025-07-01 07:48:20 +00:00
Add commands and key bindings
This commit is contained in:
parent
c6c7bddb2a
commit
2b0f190f15
271
src/command.go
Normal file
271
src/command.go
Normal 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"] = ©Cmd
|
||||
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
133
src/keybindings.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
105
src/top_menu.go
105
src/top_menu.go
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user