Add configuration files for styles, keybindings and other options

This commit is contained in:
EnumDev 2025-06-12 21:01:37 +03:00
parent 12649af8e3
commit 444c355117
18 changed files with 450 additions and 99 deletions

View File

@ -13,8 +13,10 @@ build:
install: build/typer
# Create directories
install -dm755 $(DESTDIR)$(BINDIR)
install -dm755 $(DESTDIR)$(SYSCONFDIR)
# Install files
install -Dm755 build/typer $(DESTDIR)$(BINDIR)/typer
cp -r config -T $(DESTDIR)$(SYSCONFDIR)/typer
uninstall:
rm $(DESTDIR)$(BINDIR)/typer

6
config/config.yml Normal file
View File

@ -0,0 +1,6 @@
# Editor style option
selected_style: "default" # Style for 256-color and true-color capable terminals
selected_style_fallback: "default-fallback" # Style for 8-color capable terminals (Like TTYs)
# Other
tab_indentation: 4 # Length of tab characters

40
config/keybindings.yml Normal file
View File

@ -0,0 +1,40 @@
keybindings:
- keybinding: "Ctrl-Q"
cursor_modes: ["buffer"]
command: "quit"
- keybinding: "Ctrl-C"
cursor_modes: ["buffer"]
command: "copy"
- keybinding: "Ctrl-V"
cursor_modes: ["buffer"]
command: "paste"
- keybinding: "Ctrl-S"
cursor_modes: ["buffer"]
command: "save"
- keybinding: "Ctrl-O"
cursor_modes: ["buffer"]
command: "open"
- keybinding: "Ctrl-R"
cursor_modes: ["buffer"]
command: "reload"
- keybinding: "PgUp"
cursor_modes: ["buffer"]
command: "prev-buffer"
- keybinding: "PgDn"
cursor_modes: ["buffer"]
command: "prev-buffer"
- keybinding: "Ctrl-N"
cursor_modes: ["buffer"]
command: "new-buffer"
- keybinding: "Delete"
cursor_modes: ["buffer"]
command: "close-buffer"
- keybinding: "F1"
cursor_modes: ["buffer","dropdown"]
command: "menu-file"
- keybinding: "F2"
cursor_modes: ["buffer","dropdown"]
command: "menu-edit"
- keybinding: "F3"
cursor_modes: ["buffer","dropdown"]
command: "menu-buffers"

21
config/styles/classic.yml Normal file
View File

@ -0,0 +1,21 @@
# Metadata
name: "classic"
description: "Style imitating the look of classic text editors and IDEs from the and 90s"
style_type: "256-color"
# Colors
colors:
buffer_area_bg: "darkblue" # Buffer area background color
buffer_area_fg: "white" # Buffer area text color
buffer_area_sel: "blue" # Buffer area selected text and cursor background color
top_menu_bg: "245" # Top menu background color
top_menu_fg: "black" # Top menu text color
dropdown_bg: "lightgray" # Dropdown background color
dropdown_fg: "black" # Dropdown text color
dropdown_sel: "blue" # Dropdown selected option background color
line_index_bg: "247" # Line index background color
line_index_fg: "black" # Line index text color
message_bar_bg: "245" # Message bar background color
message_bar_fg: "black" # Message bar text color
input_bar_bg: "245" # Input bar background color
input_bar_fg: "black" # Input bar text color

View File

@ -0,0 +1,21 @@
# Metadata
name: "default-fallback"
description: "The default look of Typer - Fallback style"
style_type: "8-color"
# Colors
colors:
buffer_area_bg: "black" # Buffer area background color
buffer_area_fg: "white" # Buffer area text color
buffer_area_sel: "navy" # Buffer area selected text and cursor background color
top_menu_bg: "white" # Top menu background color
top_menu_fg: "black" # Top -menu text color
dropdown_bg: "white" # Dropdown background color
dropdown_fg: "black" # Dropdown text color
dropdown_sel: "navy" # Dropdown selected option background color
line_index_bg: "white" # Line index background color
line_index_fg: "black" # Line index text color
message_bar_bg: "white" # Message bar background color
message_bar_fg: "black" # Message bar text color
input_bar_bg: "white" # Input bar background color
input_bar_fg: "black" # Input bar text color

21
config/styles/default.yml Normal file
View File

@ -0,0 +1,21 @@
# Metadata
name: "default"
description: "The default look of Typer"
style_type: "256-color"
# Colors
colors:
buffer_area_bg: "234" # Buffer area background color
buffer_area_fg: "white" # Buffer area text color
buffer_area_sel: "243" # Buffer area selected text and cursor background color
top_menu_bg: "236" # Top menu background color
top_menu_fg: "white" # Top menu text color
dropdown_bg: "236" # Dropdown background color
dropdown_fg: "white" # Dropdown text color
dropdown_sel: "240" # Dropdown selected option background color
line_index_bg: "235" # Line index background color
line_index_fg: "dimgray" # Line index text color
message_bar_bg: "236" # Message bar background color
message_bar_fg: "white" # Message bar text color
input_bar_bg: "236" # Input bar background color
input_bar_fg: "white" # Input bar text color

55
src/config.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"gopkg.in/yaml.v3"
"log"
"os"
"path"
)
type TyperConfig struct {
SelectedStyle string `yaml:"selected_style,omitempty"`
FallbackStyle string `yaml:"fallback_style,omitempty"`
TabIndentation int `yaml:"tab_indentation,omitempty"`
}
var Config TyperConfig
func readConfig() {
Config = TyperConfig{
SelectedStyle: "default",
FallbackStyle: "default-fallback",
TabIndentation: 4,
}
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
}
if _, err := os.Stat(path.Join(homeDir, ".config/typer/config.yml")); err == nil {
data, err := os.ReadFile(path.Join(homeDir, ".config/typer/config.yml"))
if err != nil {
log.Fatalf("Could not read config.yml: %s", err)
}
err = yaml.Unmarshal(data, &Config)
if err != nil {
log.Fatalf("Could not unmarshal config.yml: %s", err)
}
} else if _, err := os.Stat("/etc/typer/config.yml"); err == nil {
reader, err := os.Open("/etc/typer/config.yml")
if err != nil {
log.Fatalf("Could not read config.yml: %s", err)
}
err = yaml.NewDecoder(reader).Decode(&Config)
if err != nil {
log.Fatalf("Could not read config.yml: %s", err)
}
reader.Close()
}
// Validate config options
if Config.TabIndentation < 1 {
Config.TabIndentation = 1
}
}

View File

@ -50,13 +50,13 @@ func ClearDropdowns() {
}
func drawDropdowns(window *Window) {
dropdownStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color236)
dropdownStyle := tcell.StyleDefault.Background(CurrentStyle.DropdownBg).Foreground(CurrentStyle.DropdownFg)
for _, d := range dropdowns {
drawBox(window.screen, d.PosX, d.PosY, d.PosX+d.Width+1, d.PosY+len(d.Options)+1, dropdownStyle)
line := d.PosY
for i, option := range d.Options {
if d.Selected == i {
drawText(window.screen, d.PosX+1, d.PosY+line, d.PosX+d.Width+1, d.PosY+line, dropdownStyle.Background(tcell.Color240), option)
drawText(window.screen, d.PosX+1, d.PosY+line, d.PosX+d.Width+1, d.PosY+line, dropdownStyle.Background(CurrentStyle.DropdownSel), option)
} else {
drawText(window.screen, d.PosX+1, d.PosY+line, d.PosX+d.Width+1, d.PosY+line, dropdownStyle, option)
}

View File

@ -12,4 +12,5 @@ require (
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -79,3 +79,6 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -42,7 +42,7 @@ func drawInputBar(window *Window) {
screen := window.screen
inputBarStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color236)
inputBarStyle := tcell.StyleDefault.Background(CurrentStyle.InputBarBg).Foreground(CurrentStyle.InputBarFg)
sizeX, sizeY := screen.Size()

View File

@ -2,106 +2,86 @@ package main
import (
"github.com/gdamore/tcell/v2"
"gopkg.in/yaml.v3"
"log"
"os"
"path"
"strings"
)
type TyperKeybindings struct {
Keybindings []Keybinding `yaml:"keybindings"`
}
type Keybinding struct {
keybind string
cursorModes []CursorMode
command string
Keybinding string `yaml:"keybinding"`
CursorModes []string `yaml:"cursor_modes"`
Command string `yaml:"command"`
}
var Keybinds = make([]Keybinding, 0)
var Keybindings TyperKeybindings
func initKeybindings() {
// Add key bindings
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-Q",
cursorModes: []CursorMode{CursorModeBuffer},
command: "quit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-C",
cursorModes: []CursorMode{CursorModeBuffer},
command: "copy",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-V",
cursorModes: []CursorMode{CursorModeBuffer},
command: "paste",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-S",
cursorModes: []CursorMode{CursorModeBuffer},
command: "save",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-O",
cursorModes: []CursorMode{CursorModeBuffer},
command: "open",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-R",
cursorModes: []CursorMode{CursorModeBuffer},
command: "reload",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "PgUp",
cursorModes: []CursorMode{CursorModeBuffer},
command: "prev-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "PgDn",
cursorModes: []CursorMode{CursorModeBuffer},
command: "next-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-N",
cursorModes: []CursorMode{CursorModeBuffer},
command: "new-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Delete",
cursorModes: []CursorMode{CursorModeBuffer},
command: "close-buffer",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "Ctrl-Q",
cursorModes: []CursorMode{CursorModeBuffer},
command: "quit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F1",
cursorModes: []CursorMode{CursorModeBuffer, CursorModeDropdown},
command: "menu-file",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F2",
cursorModes: []CursorMode{CursorModeBuffer, CursorModeDropdown},
command: "menu-edit",
})
Keybinds = append(Keybinds, Keybinding{
keybind: "F3",
cursorModes: []CursorMode{CursorModeBuffer, CursorModeDropdown},
command: "menu-buffers",
})
func readKeybindings() {
Keybindings = TyperKeybindings{
Keybindings: make([]Keybinding, 0),
}
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
}
if _, err := os.Stat(path.Join(homeDir, ".config/typer/keybindings.yml")); err == nil {
data, err := os.ReadFile(path.Join(homeDir, ".config/typer/keybindings.yml"))
if err != nil {
log.Fatalf("Could not read keybindings.yml: %s", err)
}
err = yaml.Unmarshal(data, &Keybindings)
if err != nil {
log.Fatalf("Could not unmarshal keybindings.yml: %s", err)
}
} else if _, err := os.Stat("/etc/typer/keybindings.yml"); err == nil {
reader, err := os.Open("/etc/typer/keybindings.yml")
if err != nil {
log.Fatalf("Could not read keybindings.yml: %s", err)
}
err = yaml.NewDecoder(reader).Decode(&Keybindings)
if err != nil {
log.Fatalf("Could not read keybindings.yml: %s", err)
}
reader.Close()
}
}
func (keybind *Keybinding) IsPressed(ev *tcell.EventKey) bool {
keys := strings.SplitN(keybind.keybind, "+", 2)
func (keybinding *Keybinding) GetCursorModes() []CursorMode {
ret := make([]CursorMode, 0)
for _, cursorModeStr := range keybinding.CursorModes {
for key, value := range CursorModeNames {
if cursorModeStr == value {
ret = append(ret, key)
}
}
}
return ret
}
func (keybinding *Keybinding) IsPressed(ev *tcell.EventKey) bool {
keys := strings.SplitN(keybinding.Keybinding, "+", 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 keybinding.Keybinding == v {
if ev.Key() == k {
return true
}
}
} else {
if keybind.keybind == string(ev.Rune()) {
if keybinding.Keybinding == string(ev.Rune()) {
return true
}
}

View File

@ -10,7 +10,7 @@ func drawLineIndex(window *Window) {
screen := window.screen
buffer := window.CurrentBuffer
lineIndexStyle := tcell.StyleDefault.Foreground(tcell.ColorDimGray).Background(tcell.Color235)
lineIndexStyle := tcell.StyleDefault.Background(CurrentStyle.LineIndexBg).Foreground(CurrentStyle.LineIndexFg)
_, sizeY := screen.Size()

View File

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

View File

@ -33,7 +33,7 @@ func PrintMessage(window *Window, message string) {
func drawMessageBar(window *Window) {
screen := window.screen
messageBarStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color236)
messageBarStyle := tcell.StyleDefault.Background(CurrentStyle.MessageBarBg).Foreground(CurrentStyle.MessageBarFg)
sizeX, sizeY := screen.Size()

187
src/style.go Normal file
View File

@ -0,0 +1,187 @@
package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"gopkg.in/yaml.v3"
"log"
"os"
"path"
"reflect"
"slices"
"strconv"
"strings"
)
type TyperStyle struct {
// Metadata
Name string
Description string
StyleType string
// Colors
BufferAreaBg tcell.Color `name:"buffer_area_bg"`
BufferAreaFg tcell.Color `name:"buffer_area_fg"`
BufferAreaSel tcell.Color `name:"buffer_area_sel"`
TopMenuBg tcell.Color `name:"top_menu_bg"`
TopMenuFg tcell.Color `name:"top_menu_fg"`
DropdownBg tcell.Color `name:"dropdown_bg"`
DropdownFg tcell.Color `name:"dropdown_fg"`
DropdownSel tcell.Color `name:"dropdown_sel"`
LineIndexBg tcell.Color `name:"line_index_bg"`
LineIndexFg tcell.Color `name:"line_index_fg"`
MessageBarBg tcell.Color `name:"message_bar_bg"`
MessageBarFg tcell.Color `name:"message_bar_fg"`
InputBarBg tcell.Color `name:"input_bar_bg"`
InputBarFg tcell.Color `name:"input_bar_fg"`
}
type typerStyleYaml struct {
// Metadata
Name string `yaml:"name"`
Description string `yaml:"description"`
StyleType string `yaml:"style_type"`
// Colors
Colors map[string]string `yaml:"colors"`
}
var AvailableStyles = make(map[string]TyperStyle)
var CurrentStyle TyperStyle
func readStyles() {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
}
if stat, err := os.Stat(path.Join(homeDir, ".config/typer/styles/")); err == nil && stat.IsDir() {
entries, err := os.ReadDir(path.Join(homeDir, ".config/typer/styles/"))
if err != nil {
log.Fatalf("Could not read user style directory: %s", err)
}
for _, entry := range entries {
entryPath := path.Join(homeDir, ".config/typer/styles/", entry.Name())
style, err := readStyleYamlFile(entryPath)
if err != nil {
log.Fatalf("Could not read style file (%s): %s", entryPath, err)
}
if _, ok := AvailableStyles[style.Name]; !ok {
AvailableStyles[style.Name] = style
}
}
}
if stat, err := os.Stat("/etc/typer/styles/"); err == nil && stat.IsDir() {
entries, err := os.ReadDir("/etc/typer/styles/")
if err != nil {
log.Fatalf("Could not read user style directory: %s", err)
}
for _, entry := range entries {
entryPath := path.Join("/etc/typer/styles/", entry.Name())
style, err := readStyleYamlFile(entryPath)
if err != nil {
log.Fatalf("Could not read style file (%s): %s", entryPath, err)
}
if _, ok := AvailableStyles[style.Name]; !ok {
AvailableStyles[style.Name] = style
}
}
}
}
func readStyleYamlFile(filepath string) (TyperStyle, error) {
styleYaml := typerStyleYaml{}
data, err := os.ReadFile(filepath)
if err != nil {
return TyperStyle{}, fmt.Errorf("could not read file: %s", err)
}
err = yaml.Unmarshal(data, &styleYaml)
if err != nil {
return TyperStyle{}, fmt.Errorf("could not unmarshal style: %s", err)
}
style := TyperStyle{
Name: styleYaml.Name,
Description: styleYaml.Description,
StyleType: styleYaml.StyleType,
}
for name, colorStr := range styleYaml.Colors {
var color tcell.Color
if n, err := strconv.Atoi(colorStr); err == nil && n >= 0 && n < 256 {
color = tcell.ColorValid + tcell.Color(n)
} else if strings.HasPrefix(colorStr, "#") && len(colorStr) == 7 {
n, err := strconv.ParseInt(colorStr[1:], 16, 32)
if err != nil {
return TyperStyle{}, fmt.Errorf("could not parse color (%s): %s", colorStr, err)
}
color = tcell.NewHexColor(int32(n))
} else if c, ok := tcell.ColorNames[colorStr]; ok {
color = c
} else {
return TyperStyle{}, fmt.Errorf("could not parse color (%s): %s", colorStr, err)
}
pt := reflect.TypeOf(&style)
t := pt.Elem()
pv := reflect.ValueOf(&style)
v := pv.Elem()
for i := 0; i < t.NumField(); i++ {
field := v.Field(i)
if tag, ok := t.Field(i).Tag.Lookup("name"); ok && tag == name {
field.Set(reflect.ValueOf(color))
}
}
}
return style, nil
}
func SetCurrentStyle(screen tcell.Screen) {
availableTypes := make([]string, 1)
availableTypes[0] = "8-color"
if screen.Colors() >= 16 {
availableTypes = append(availableTypes, "16-color")
}
if screen.Colors() >= 256 {
availableTypes = append(availableTypes, "256-color")
}
if screen.Colors() >= 16777216 {
availableTypes = append(availableTypes, "true-color")
}
if style, ok := AvailableStyles[Config.SelectedStyle]; ok && slices.Index(availableTypes, style.StyleType) != -1 {
CurrentStyle = style
} else if style, ok := AvailableStyles[Config.FallbackStyle]; ok {
CurrentStyle = style
} else {
CurrentStyle = TyperStyle{
Name: "fallback",
Description: "Fallback style",
StyleType: "8-color",
BufferAreaBg: tcell.ColorBlack,
BufferAreaFg: tcell.ColorWhite,
BufferAreaSel: tcell.ColorNavy,
TopMenuBg: tcell.ColorWhite,
TopMenuFg: tcell.ColorBlack,
DropdownBg: tcell.ColorWhite,
DropdownFg: tcell.ColorBlack,
DropdownSel: tcell.ColorNavy,
LineIndexBg: tcell.ColorWhite,
LineIndexFg: tcell.ColorBlack,
MessageBarBg: tcell.ColorWhite,
MessageBarFg: tcell.ColorBlack,
InputBarBg: tcell.ColorWhite,
InputBarFg: tcell.ColorBlack,
}
}
}

View File

@ -100,7 +100,7 @@ func initTopMenu() {
func drawTopMenu(window *Window) {
screen := window.screen
topMenuStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color236)
topMenuStyle := tcell.StyleDefault.Background(CurrentStyle.TopMenuBg).Foreground(CurrentStyle.TopMenuFg)
sizeX, _ := screen.Size()

View File

@ -16,6 +16,13 @@ const (
CursorModeInputBar
)
var CursorModeNames = map[CursorMode]string{
CursorModeDisabled: "disabled",
CursorModeBuffer: "buffer",
CursorModeDropdown: "dropdown",
CursorModeInputBar: "input_bar",
}
type Window struct {
ShowTopMenu bool
ShowLineIndex bool
@ -57,7 +64,8 @@ func CreateWindow() (*Window, error) {
}
// Set screen style
screen.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color234))
SetCurrentStyle(screen)
screen.SetStyle(tcell.StyleDefault.Foreground(CurrentStyle.BufferAreaFg).Background(CurrentStyle.BufferAreaBg))
// Enable mouse
screen.EnableMouse()
@ -81,21 +89,21 @@ func (window *Window) drawCurrentBuffer() {
for i, r := range buffer.Contents + " " {
if x-buffer.OffsetX >= bufferX && y-buffer.OffsetY >= bufferY {
// Default style
style := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color234)
style := tcell.StyleDefault.Background(CurrentStyle.BufferAreaBg).Foreground(CurrentStyle.BufferAreaFg)
// Change background if under cursor
if i == buffer.CursorPos {
style = style.Background(tcell.Color243)
style = style.Background(CurrentStyle.BufferAreaSel)
}
// Change background if selected
if buffer.Selection != nil {
if edge1, edge2 := buffer.GetSelectionEdges(); i >= edge1 && i <= edge2 {
style = style.Background(tcell.Color243)
style = style.Background(CurrentStyle.BufferAreaSel)
// Show selection on entire tab space
if r == '\t' {
for j := 0; j < 4; j++ {
for j := 0; j < int(Config.TabIndentation); j++ {
window.screen.SetContent(x+j-buffer.OffsetX, y-buffer.OffsetY, r, nil, style)
}
}
@ -110,7 +118,7 @@ func (window *Window) drawCurrentBuffer() {
x = bufferX
y++
} else if r == '\t' {
x += 4
x += int(Config.TabIndentation)
} else {
x++
}
@ -327,9 +335,9 @@ func (window *Window) input(ev *tcell.EventKey) {
}
// Check key bindings
for _, keybinding := range Keybinds {
if keybinding.IsPressed(ev) && slices.Index(keybinding.cursorModes, window.CursorMode) != -1 {
RunCommand(window, keybinding.command)
for _, keybinding := range Keybindings.Keybindings {
if keybinding.IsPressed(ev) && slices.Index(keybinding.GetCursorModes(), window.CursorMode) != -1 {
RunCommand(window, keybinding.Command)
return
}
}
@ -603,7 +611,7 @@ func (window *Window) AbsolutePosToCursorPos2D(x, y int) (int, int) {
posInLine := make([]int, 0)
for i, char := range []rune(line) {
if char == '\t' {
for j := 0; j < 4; j++ {
for j := 0; j < Config.TabIndentation; j++ {
posInLine = append(posInLine, i)
}
} else {