mirror of
https://github.com/EnumeratedDev/typer.git
synced 2026-09-26 18:06:11 +00:00
Compare commits
12
Commits
4ca6d9b75b
...
0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d49f84d6f | ||
|
|
ffd8cd54c0 | ||
|
|
12495d4bd9 | ||
|
|
51ec45cd14 | ||
|
|
cc3b4ecf18 | ||
|
|
1b6b45aaea | ||
|
|
e2ac216d6e | ||
|
|
cd823c9d9f | ||
|
|
dd0cc2a293 | ||
|
|
444c355117 | ||
|
|
12649af8e3 | ||
|
|
5d64673519 |
@@ -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
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Typer Text Editor
|
||||
### A simple and easy to use text editor written in Go
|
||||
|
||||
| Default Style | Classic Style |
|
||||
|:----------------------------------------------------------------:|:----------------------------------------------------------------:|
|
||||
|  |  |
|
||||
|
||||
### Installation
|
||||
#### From source:
|
||||
- Download `go` from your package manager or from the go website
|
||||
- Downlaod `which` from your package manager
|
||||
- Download `make` from your package manager
|
||||
- Run the following command to compile Typer
|
||||
```shell
|
||||
make
|
||||
```
|
||||
- Run the following command **with superuser privileges** to install Typer to your system
|
||||
```shell
|
||||
make install SYSCONFDIR=/etc
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
show_top_menu: true
|
||||
show_line_index: true
|
||||
buffer_info_message: "File: %f Cursor: (%x, %y, %p) Chars: %c"
|
||||
tab_indentation: 4 # Length of tab characters
|
||||
@@ -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: "next-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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
+94
-11
@@ -2,13 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
Id int
|
||||
Name string
|
||||
Contents string
|
||||
|
||||
@@ -26,8 +26,71 @@ type Selection struct {
|
||||
selectionEnd int
|
||||
}
|
||||
|
||||
var Buffers = make(map[int]*Buffer)
|
||||
var LastBufferId int
|
||||
var Buffers = make([]*Buffer, 0)
|
||||
|
||||
func GetBufferByName(name string) *Buffer {
|
||||
for _, buffer := range Buffers {
|
||||
if buffer.Name == name {
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetBufferByFilename(filename string) *Buffer {
|
||||
for _, buffer := range Buffers {
|
||||
if buffer.filename == filename {
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func drawBuffer(window *Window) {
|
||||
buffer := window.CurrentBuffer
|
||||
|
||||
x, y, _, _ := window.GetTextAreaDimensions()
|
||||
|
||||
bufferX, bufferY, _, _ := window.GetTextAreaDimensions()
|
||||
|
||||
for i, r := range buffer.Contents + " " {
|
||||
if x-buffer.OffsetX >= bufferX && y-buffer.OffsetY >= bufferY {
|
||||
// Default style
|
||||
style := tcell.StyleDefault.Background(CurrentStyle.BufferAreaBg).Foreground(CurrentStyle.BufferAreaFg)
|
||||
|
||||
// Change background if under cursor
|
||||
if i == buffer.CursorPos {
|
||||
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(CurrentStyle.BufferAreaSel)
|
||||
|
||||
// Show selection on entire tab space
|
||||
if r == '\t' {
|
||||
for j := 0; j < int(Config.TabIndentation); j++ {
|
||||
window.screen.SetContent(x+j-buffer.OffsetX, y-buffer.OffsetY, r, nil, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.screen.SetContent(x-buffer.OffsetX, y-buffer.OffsetY, r, nil, style)
|
||||
}
|
||||
|
||||
// Change position for next character
|
||||
if r == '\n' {
|
||||
x = bufferX
|
||||
y++
|
||||
} else if r == '\t' {
|
||||
x += int(Config.TabIndentation)
|
||||
} else {
|
||||
x++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) Load() error {
|
||||
// Do not load if canSave is false or filename is not set
|
||||
@@ -63,6 +126,18 @@ func (buffer *Buffer) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (buffer *Buffer) GetSelectionEdges() (int, int) {
|
||||
if buffer.Selection == nil {
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
if buffer.Selection.selectionStart < buffer.Selection.selectionEnd {
|
||||
return buffer.Selection.selectionStart, buffer.Selection.selectionEnd
|
||||
} else {
|
||||
return buffer.Selection.selectionEnd, buffer.Selection.selectionStart
|
||||
}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) GetSelectedText() string {
|
||||
if buffer.Selection == nil {
|
||||
return ""
|
||||
@@ -145,8 +220,15 @@ func CreateFileBuffer(filename string, openNonExistentFile bool) (*Buffer, error
|
||||
}
|
||||
}
|
||||
|
||||
if GetBufferByName(filename) != nil {
|
||||
return nil, fmt.Errorf("a buffer with the name (%s) is already open", filename)
|
||||
}
|
||||
|
||||
if GetBufferByFilename(abs) != nil {
|
||||
return nil, fmt.Errorf("%s is already open in another buffer", filename)
|
||||
}
|
||||
|
||||
buffer := Buffer{
|
||||
Id: LastBufferId + 1,
|
||||
Name: filename,
|
||||
Contents: "",
|
||||
CursorPos: 0,
|
||||
@@ -163,15 +245,13 @@ func CreateFileBuffer(filename string, openNonExistentFile bool) (*Buffer, error
|
||||
}
|
||||
}
|
||||
|
||||
Buffers[buffer.Id] = &buffer
|
||||
LastBufferId++
|
||||
Buffers = append(Buffers, &buffer)
|
||||
|
||||
return &buffer, nil
|
||||
}
|
||||
|
||||
func CreateBuffer(bufferName string) *Buffer {
|
||||
func CreateBuffer(bufferName string) (*Buffer, error) {
|
||||
buffer := Buffer{
|
||||
Id: LastBufferId + 1,
|
||||
Name: bufferName,
|
||||
Contents: "",
|
||||
CursorPos: 0,
|
||||
@@ -179,8 +259,11 @@ func CreateBuffer(bufferName string) *Buffer {
|
||||
filename: "",
|
||||
}
|
||||
|
||||
Buffers[buffer.Id] = &buffer
|
||||
LastBufferId++
|
||||
if GetBufferByName(bufferName) != nil {
|
||||
return nil, fmt.Errorf("a buffer with the name (%s) is already open", bufferName)
|
||||
}
|
||||
|
||||
return &buffer
|
||||
Buffers = append(Buffers, &buffer)
|
||||
|
||||
return &buffer, nil
|
||||
}
|
||||
|
||||
+25
-19
@@ -3,8 +3,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -140,15 +140,15 @@ func initCommands() {
|
||||
return
|
||||
}
|
||||
|
||||
buffers := slices.Collect(maps.Values(Buffers))
|
||||
index := slices.Index(buffers, window.CurrentBuffer)
|
||||
index := slices.Index(Buffers, window.CurrentBuffer)
|
||||
|
||||
index--
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
|
||||
window.CurrentBuffer = buffers[index]
|
||||
window.CurrentBuffer = Buffers[index]
|
||||
PrintMessage(window, fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -159,44 +159,50 @@ func initCommands() {
|
||||
return
|
||||
}
|
||||
|
||||
buffers := slices.Collect(maps.Values(Buffers))
|
||||
index := slices.Index(buffers, window.CurrentBuffer)
|
||||
index := slices.Index(Buffers, window.CurrentBuffer)
|
||||
|
||||
index++
|
||||
if index >= len(buffers) {
|
||||
index = len(buffers) - 1
|
||||
if index >= len(Buffers) {
|
||||
index = len(Buffers) - 1
|
||||
}
|
||||
|
||||
window.CurrentBuffer = buffers[index]
|
||||
window.CurrentBuffer = Buffers[index]
|
||||
PrintMessage(window, fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
|
||||
},
|
||||
}
|
||||
|
||||
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++
|
||||
for i := 1; true; i++ {
|
||||
buffer, err := CreateBuffer("New Buffer " + strconv.Itoa(i))
|
||||
if err == nil {
|
||||
window.CurrentBuffer = buffer
|
||||
break
|
||||
}
|
||||
}
|
||||
buffer := CreateBuffer(fmt.Sprintf("New File %d", number))
|
||||
window.CurrentBuffer = buffer
|
||||
|
||||
window.CursorMode = CursorModeBuffer
|
||||
PrintMessage(window, fmt.Sprintf("New buffer created with the name '%s'.", window.CurrentBuffer.Name))
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
bufferIndex := slices.Index(Buffers, window.CurrentBuffer)
|
||||
Buffers = DeleteFromSlice(Buffers, bufferIndex)
|
||||
if len(Buffers) == 0 {
|
||||
window.Close()
|
||||
return
|
||||
}
|
||||
window.CurrentBuffer = buffersSlice[0]
|
||||
if bufferIndex >= len(Buffers) {
|
||||
window.CurrentBuffer = Buffers[bufferIndex-1]
|
||||
} else {
|
||||
window.CurrentBuffer = Buffers[bufferIndex]
|
||||
}
|
||||
window.CursorMode = CursorModeBuffer
|
||||
PrintMessage(window, "Buffer closed.")
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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"`
|
||||
ShowTopMenu bool `yaml:"show_top_menu,omitempty"`
|
||||
ShowLineIndex bool `yaml:"show_line_index,omitempty"`
|
||||
BufferInfoMessage string `yaml:"buffer_info_message,omitempty"`
|
||||
TabIndentation int `yaml:"tab_indentation,omitempty"`
|
||||
}
|
||||
|
||||
var Config TyperConfig
|
||||
|
||||
func readConfig() {
|
||||
Config = TyperConfig{
|
||||
SelectedStyle: "default",
|
||||
FallbackStyle: "default-fallback",
|
||||
ShowTopMenu: true,
|
||||
ShowLineIndex: true,
|
||||
BufferInfoMessage: "File: %f Cursor: (%x, %y, %p) Chars: %c",
|
||||
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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
line := 1
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
+1
-5
@@ -31,10 +31,6 @@ func RequestInput(window *Window, text string, defaultInput string) chan string
|
||||
return request.inputChannel
|
||||
}
|
||||
|
||||
func IsRequestingInput() bool {
|
||||
return currentInputRequest != nil
|
||||
}
|
||||
|
||||
func drawInputBar(window *Window) {
|
||||
if currentInputRequest == nil {
|
||||
return
|
||||
@@ -42,7 +38,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()
|
||||
|
||||
|
||||
+60
-80
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
|
||||
+12
-6
@@ -6,28 +6,34 @@ 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)
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
for _, file := range os.Args[1:] {
|
||||
for i, file := range os.Args[1:] {
|
||||
b, err := CreateFileBuffer(file, true)
|
||||
if err != nil {
|
||||
PrintMessage(window, "Could not open file: "+file)
|
||||
continue
|
||||
}
|
||||
|
||||
if window.CurrentBuffer.Name == "New File 1" {
|
||||
delete(Buffers, window.CurrentBuffer.Id)
|
||||
if i == 0 {
|
||||
window.CurrentBuffer = b
|
||||
Buffers = Buffers[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
-22
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -22,7 +21,13 @@ func initTopMenu() {
|
||||
Name: "File",
|
||||
Action: func(window *Window) {
|
||||
ClearDropdowns()
|
||||
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, 0, 1, 0, func(i int) {
|
||||
|
||||
y := 0
|
||||
if window.ShowTopMenu {
|
||||
y++
|
||||
}
|
||||
|
||||
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, 0, y, 0, func(i int) {
|
||||
switch i {
|
||||
case 0:
|
||||
RunCommand(window, "new-buffer")
|
||||
@@ -45,7 +50,13 @@ func initTopMenu() {
|
||||
Name: "Edit",
|
||||
Action: func(window *Window) {
|
||||
ClearDropdowns()
|
||||
d := CreateDropdownMenu([]string{"Copy", "Paste"}, 0, 1, 0, func(i int) {
|
||||
|
||||
y := 0
|
||||
if window.ShowTopMenu {
|
||||
y++
|
||||
}
|
||||
|
||||
d := CreateDropdownMenu([]string{"Copy", "Paste"}, 0, y, 0, func(i int) {
|
||||
switch i {
|
||||
case 0:
|
||||
RunCommand(window, "copy")
|
||||
@@ -63,28 +74,24 @@ func initTopMenu() {
|
||||
Name: "Buffers",
|
||||
Action: func(window *Window) {
|
||||
ClearDropdowns()
|
||||
|
||||
y := 0
|
||||
if window.ShowTopMenu {
|
||||
y++
|
||||
}
|
||||
|
||||
buffersSlice := make([]string, 0)
|
||||
for _, buffer := range Buffers {
|
||||
for i, buffer := range Buffers {
|
||||
if window.CurrentBuffer == buffer {
|
||||
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] * %s", buffer.Id, buffer.Name))
|
||||
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] * %s", i+1, buffer.Name))
|
||||
} else {
|
||||
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] %s", buffer.Id, buffer.Name))
|
||||
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] %s", i+1, buffer.Name))
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(buffersSlice)
|
||||
|
||||
d := CreateDropdownMenu(buffersSlice, 0, 1, 0, func(i int) {
|
||||
start := strings.Index(buffersSlice[i], "[")
|
||||
end := strings.Index(buffersSlice[i], "]")
|
||||
|
||||
id, err := strconv.Atoi(buffersSlice[i][start+1 : end])
|
||||
if err != nil {
|
||||
PrintMessage(window, fmt.Sprintf("Cannot convert buffer id '%s' to int", buffersSlice[i][start:end]))
|
||||
return
|
||||
}
|
||||
|
||||
window.CurrentBuffer = Buffers[id]
|
||||
d := CreateDropdownMenu(buffersSlice, 0, y, 0, func(i int) {
|
||||
window.CurrentBuffer = Buffers[i]
|
||||
PrintMessage(window, fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
|
||||
ClearDropdowns()
|
||||
window.CursorMode = CursorModeBuffer
|
||||
})
|
||||
@@ -100,7 +107,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()
|
||||
|
||||
@@ -115,11 +122,38 @@ func drawTopMenu(window *Window) {
|
||||
}
|
||||
|
||||
// Draw buffer info
|
||||
bufferInfoMsg := getBufferInfoMsg(window)
|
||||
drawText(screen, sizeX-len(bufferInfoMsg)-1, 0, sizeX-1, 0, topMenuStyle, bufferInfoMsg)
|
||||
}
|
||||
|
||||
func getBufferInfoMsg(window *Window) string {
|
||||
pathToFile := "Not set"
|
||||
filename := "Not set"
|
||||
if window.CurrentBuffer.filename != "" {
|
||||
pathToFile = window.CurrentBuffer.filename
|
||||
}
|
||||
if filepath.Base(window.CurrentBuffer.filename) != "." {
|
||||
filename = filepath.Base(window.CurrentBuffer.filename)
|
||||
}
|
||||
|
||||
cursorPos := window.CurrentBuffer.CursorPos
|
||||
cursorX, cursorY := window.GetCursorPos2D()
|
||||
cursorInfo := fmt.Sprintf("File: %s Cursor: (%d,%d,%d) Words: %d", filename, cursorX+1, cursorY+1, window.CurrentBuffer.CursorPos+1, len(strings.Fields(window.CurrentBuffer.Contents)))
|
||||
drawText(screen, sizeX-len(cursorInfo)-1, 0, sizeX-1, 0, topMenuStyle, cursorInfo)
|
||||
cursorX++
|
||||
cursorY++
|
||||
|
||||
chars := len(window.CurrentBuffer.Contents)
|
||||
words := len(strings.Fields(window.CurrentBuffer.Contents))
|
||||
|
||||
ret := Config.BufferInfoMessage
|
||||
|
||||
ret = strings.ReplaceAll(ret, "\n", " ")
|
||||
ret = strings.ReplaceAll(ret, "%F", pathToFile)
|
||||
ret = strings.ReplaceAll(ret, "%f", filename)
|
||||
ret = strings.ReplaceAll(ret, "%x", strconv.Itoa(cursorX))
|
||||
ret = strings.ReplaceAll(ret, "%y", strconv.Itoa(cursorY))
|
||||
ret = strings.ReplaceAll(ret, "%p", strconv.Itoa(cursorPos))
|
||||
ret = strings.ReplaceAll(ret, "%c", strconv.Itoa(chars))
|
||||
ret = strings.ReplaceAll(ret, "%w", strconv.Itoa(words))
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -53,3 +53,15 @@ func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style) {
|
||||
|
||||
drawText(s, x1+1, y1+1, x2-1, y2-1, style, " ")
|
||||
}
|
||||
|
||||
func DeleteFromSlice[T any](slice []T, i int) []T {
|
||||
if i >= len(slice) {
|
||||
return slice
|
||||
} else if i < 0 {
|
||||
return slice
|
||||
} else if i == len(slice)-1 {
|
||||
return slice[:len(slice)-1]
|
||||
} else {
|
||||
return append(slice[:i], slice[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
+294
-90
@@ -4,6 +4,10 @@ import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"log"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type CursorMode uint8
|
||||
@@ -15,6 +19,13 @@ const (
|
||||
CursorModeInputBar
|
||||
)
|
||||
|
||||
var CursorModeNames = map[CursorMode]string{
|
||||
CursorModeDisabled: "disabled",
|
||||
CursorModeBuffer: "buffer",
|
||||
CursorModeDropdown: "dropdown",
|
||||
CursorModeInputBar: "input_bar",
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
ShowTopMenu bool
|
||||
ShowLineIndex bool
|
||||
@@ -28,11 +39,12 @@ type Window struct {
|
||||
}
|
||||
|
||||
var mouseHeld = false
|
||||
var lastClick int64 = 0
|
||||
|
||||
func CreateWindow() (*Window, error) {
|
||||
window := Window{
|
||||
ShowTopMenu: true,
|
||||
ShowLineIndex: true,
|
||||
ShowTopMenu: Config.ShowTopMenu,
|
||||
ShowLineIndex: Config.ShowLineIndex,
|
||||
CursorMode: CursorModeBuffer,
|
||||
|
||||
CurrentBuffer: nil,
|
||||
@@ -41,8 +53,11 @@ func CreateWindow() (*Window, error) {
|
||||
}
|
||||
|
||||
// Create empty buffer if nil
|
||||
if window.CurrentBuffer == nil {
|
||||
window.CurrentBuffer = CreateBuffer("New File 1")
|
||||
for i := 1; window.CurrentBuffer == nil; i++ {
|
||||
buffer, err := CreateBuffer("New Buffer " + strconv.Itoa(i))
|
||||
if err == nil {
|
||||
window.CurrentBuffer = buffer
|
||||
}
|
||||
}
|
||||
|
||||
// Create tcell screen
|
||||
@@ -56,7 +71,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()
|
||||
@@ -70,58 +86,6 @@ func CreateWindow() (*Window, error) {
|
||||
return &window, nil
|
||||
}
|
||||
|
||||
func (window *Window) drawCurrentBuffer() {
|
||||
buffer := window.CurrentBuffer
|
||||
|
||||
x, y, _, _ := window.GetTextAreaDimensions()
|
||||
|
||||
bufferX, bufferY, _, _ := window.GetTextAreaDimensions()
|
||||
|
||||
normalStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color234)
|
||||
selectedStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color243)
|
||||
|
||||
for i, r := range buffer.Contents {
|
||||
if r == '\n' {
|
||||
x = 0
|
||||
if window.ShowLineIndex {
|
||||
x += bufferX
|
||||
}
|
||||
y++
|
||||
}
|
||||
|
||||
if r != '\n' {
|
||||
x++
|
||||
}
|
||||
|
||||
if x-buffer.OffsetX-1 < bufferX {
|
||||
continue
|
||||
}
|
||||
if y-buffer.OffsetY < bufferY {
|
||||
continue
|
||||
}
|
||||
|
||||
if buffer.Selection != nil && buffer.Selection.selectionEnd >= buffer.Selection.selectionStart && i >= buffer.Selection.selectionStart && i <= buffer.Selection.selectionEnd {
|
||||
window.screen.SetContent(x-buffer.OffsetX-1, y-buffer.OffsetY, r, nil, selectedStyle)
|
||||
} else if buffer.Selection != nil && i <= buffer.Selection.selectionStart && i >= buffer.Selection.selectionEnd {
|
||||
window.screen.SetContent(x-buffer.OffsetX-1, y-buffer.OffsetY, r, nil, selectedStyle)
|
||||
} else {
|
||||
window.screen.SetContent(x-buffer.OffsetX-1, y-buffer.OffsetY, r, nil, normalStyle)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw cursor
|
||||
cursorX, cursorY := window.GetCursorPos2D()
|
||||
|
||||
cursorX += bufferX
|
||||
cursorY += bufferY
|
||||
|
||||
cursorX -= window.CurrentBuffer.OffsetX
|
||||
cursorY -= window.CurrentBuffer.OffsetY
|
||||
|
||||
r, _, _, _ := window.screen.GetContent(cursorX, cursorY)
|
||||
window.screen.SetContent(cursorX, cursorY, r, nil, selectedStyle)
|
||||
}
|
||||
|
||||
func (window *Window) Draw() {
|
||||
// Clear screen
|
||||
window.screen.Clear()
|
||||
@@ -138,7 +102,7 @@ func (window *Window) Draw() {
|
||||
|
||||
// Draw current buffer
|
||||
if window.CurrentBuffer != nil {
|
||||
window.drawCurrentBuffer()
|
||||
drawBuffer(window)
|
||||
}
|
||||
|
||||
// Draw input bar
|
||||
@@ -181,16 +145,47 @@ func (window *Window) Draw() {
|
||||
func (window *Window) input(ev *tcell.EventKey) {
|
||||
if ev.Key() == tcell.KeyRight { // Navigation Keys
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
// Get original cursor position
|
||||
pos := window.CurrentBuffer.CursorPos
|
||||
|
||||
if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
// Move cursor to start of word
|
||||
// Set variable to one character right of current position
|
||||
endOfWord := pos + 1
|
||||
if endOfWord >= len(window.CurrentBuffer.Contents) {
|
||||
endOfWord = len(window.CurrentBuffer.Contents)
|
||||
}
|
||||
|
||||
// Skip all spaces
|
||||
for endOfWord < len(window.CurrentBuffer.Contents) && unicode.IsSpace(rune(window.CurrentBuffer.Contents[endOfWord])) {
|
||||
endOfWord++
|
||||
}
|
||||
|
||||
// Find end of word
|
||||
for endOfWord < len(window.CurrentBuffer.Contents) && !unicode.IsSpace(rune(window.CurrentBuffer.Contents[endOfWord])) {
|
||||
endOfWord++
|
||||
}
|
||||
|
||||
window.SetCursorPos(endOfWord)
|
||||
} else {
|
||||
// Move cursor one character backwards
|
||||
window.SetCursorPos(window.CurrentBuffer.CursorPos + 1)
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
if ev.Modifiers() == tcell.ModShift {
|
||||
if ev.Modifiers()&tcell.ModShift != 0 {
|
||||
if window.CurrentBuffer.Selection == nil {
|
||||
// Cancel cursor movement when creating selection without holding ctrl
|
||||
if ev.Modifiers()&tcell.ModCtrl == 0 {
|
||||
window.SetCursorPos(pos)
|
||||
}
|
||||
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: window.CurrentBuffer.CursorPos,
|
||||
selectionStart: pos,
|
||||
selectionEnd: window.CurrentBuffer.CursorPos,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos + 1
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos
|
||||
}
|
||||
// Prevent selecting dummy character at the end of the buffer
|
||||
if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) {
|
||||
@@ -199,46 +194,83 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
} else if window.CurrentBuffer.Selection != nil {
|
||||
// Unset selection
|
||||
window.CurrentBuffer.Selection = nil
|
||||
return
|
||||
}
|
||||
// Move cursor
|
||||
window.SetCursorPos(window.CurrentBuffer.CursorPos + 1)
|
||||
}
|
||||
} else if ev.Key() == tcell.KeyLeft {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
// Get original cursor position
|
||||
pos := window.CurrentBuffer.CursorPos
|
||||
|
||||
if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
// Move cursor to start of word
|
||||
// Set variable to one character left of current position
|
||||
startOfWord := pos - 1
|
||||
if startOfWord < 0 {
|
||||
startOfWord = 0
|
||||
}
|
||||
|
||||
// Skip all spaces
|
||||
for startOfWord >= 0 && len(window.CurrentBuffer.Contents) != 0 && unicode.IsSpace(rune(window.CurrentBuffer.Contents[startOfWord])) {
|
||||
startOfWord--
|
||||
}
|
||||
|
||||
// Find start of word
|
||||
for startOfWord >= 0 && len(window.CurrentBuffer.Contents) != 0 && !unicode.IsSpace(rune(window.CurrentBuffer.Contents[startOfWord])) {
|
||||
startOfWord--
|
||||
}
|
||||
|
||||
// Move one character to the right
|
||||
startOfWord++
|
||||
|
||||
window.SetCursorPos(startOfWord)
|
||||
} else {
|
||||
// Move cursor one character backwards
|
||||
window.SetCursorPos(window.CurrentBuffer.CursorPos - 1)
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
if ev.Modifiers() == tcell.ModShift {
|
||||
if ev.Modifiers()&tcell.ModShift != 0 {
|
||||
if window.CurrentBuffer.Selection == nil {
|
||||
// Cancel cursor movement when creating selection without holding ctrl
|
||||
if ev.Modifiers()&tcell.ModCtrl == 0 {
|
||||
window.SetCursorPos(pos)
|
||||
}
|
||||
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: window.CurrentBuffer.CursorPos,
|
||||
selectionStart: pos,
|
||||
selectionEnd: window.CurrentBuffer.CursorPos,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos - 1
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos
|
||||
}
|
||||
} else if window.CurrentBuffer.Selection != nil {
|
||||
// Unset selection
|
||||
window.CurrentBuffer.Selection = nil
|
||||
return
|
||||
}
|
||||
// Move cursor
|
||||
window.SetCursorPos(window.CurrentBuffer.CursorPos - 1)
|
||||
}
|
||||
} else if ev.Key() == tcell.KeyUp {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
// Get original cursor position
|
||||
pos := window.CurrentBuffer.CursorPos
|
||||
// Move cursor
|
||||
x, y := window.GetCursorPos2D()
|
||||
window.SetCursorPos2D(x, y-1)
|
||||
|
||||
if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
// Move cursor to top of buffer
|
||||
window.SetCursorPos(0)
|
||||
} else {
|
||||
// Move cursor one line up
|
||||
x, y := window.GetCursorPos2D()
|
||||
window.SetCursorPos2D(x, y-1)
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
if ev.Modifiers() == tcell.ModShift {
|
||||
if ev.Modifiers()&tcell.ModShift != 0 {
|
||||
// Add to selection
|
||||
if window.CurrentBuffer.Selection == nil {
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: window.CurrentBuffer.CursorPos,
|
||||
selectionEnd: pos,
|
||||
selectionStart: pos,
|
||||
selectionEnd: window.CurrentBuffer.CursorPos,
|
||||
}
|
||||
} else {
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos
|
||||
@@ -273,11 +305,18 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
// Get original cursor position
|
||||
pos := window.CurrentBuffer.CursorPos
|
||||
// Move cursor
|
||||
x, y := window.GetCursorPos2D()
|
||||
window.SetCursorPos2D(x, y+1)
|
||||
|
||||
if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
// Move cursor to bottom of buffer
|
||||
window.SetCursorPos(len(window.CurrentBuffer.Contents))
|
||||
} else {
|
||||
// Move cursor one line down
|
||||
x, y := window.GetCursorPos2D()
|
||||
window.SetCursorPos2D(x, y+1)
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
if ev.Modifiers() == tcell.ModShift {
|
||||
if ev.Modifiers()&tcell.ModShift != 0 {
|
||||
// Add to selection
|
||||
if window.CurrentBuffer.Selection == nil {
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
@@ -332,9 +371,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
|
||||
}
|
||||
}
|
||||
@@ -345,7 +384,17 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
str := window.CurrentBuffer.Contents
|
||||
index := window.CurrentBuffer.CursorPos
|
||||
|
||||
if index != 0 {
|
||||
if window.CurrentBuffer.Selection != nil {
|
||||
edge1, edge2 := window.CurrentBuffer.GetSelectionEdges()
|
||||
if edge2 == len(window.CurrentBuffer.Contents) {
|
||||
edge2 = len(window.CurrentBuffer.Contents) - 1
|
||||
}
|
||||
|
||||
str = str[:edge1] + str[edge2+1:]
|
||||
window.CurrentBuffer.Contents = str
|
||||
window.SetCursorPos(edge1)
|
||||
window.CurrentBuffer.Selection = nil
|
||||
} else if index != 0 {
|
||||
str = str[:index-1] + str[index:]
|
||||
window.CurrentBuffer.Contents = str
|
||||
window.SetCursorPos(window.CurrentBuffer.CursorPos - 1)
|
||||
@@ -363,6 +412,20 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
} else if ev.Key() == tcell.KeyTab {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
str := window.CurrentBuffer.Contents
|
||||
|
||||
// Remove selected text
|
||||
if window.CurrentBuffer.Selection != nil {
|
||||
edge1, edge2 := window.CurrentBuffer.GetSelectionEdges()
|
||||
if edge2 == len(window.CurrentBuffer.Contents) {
|
||||
edge2 = len(window.CurrentBuffer.Contents) - 1
|
||||
}
|
||||
|
||||
str = str[:edge1] + str[edge2+1:]
|
||||
window.CurrentBuffer.Contents = str
|
||||
window.SetCursorPos(edge1)
|
||||
window.CurrentBuffer.Selection = nil
|
||||
}
|
||||
|
||||
index := window.CurrentBuffer.CursorPos
|
||||
|
||||
if index == len(str) {
|
||||
@@ -376,6 +439,20 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
} else if ev.Key() == tcell.KeyEnter {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
str := window.CurrentBuffer.Contents
|
||||
|
||||
// Remove selected text
|
||||
if window.CurrentBuffer.Selection != nil {
|
||||
edge1, edge2 := window.CurrentBuffer.GetSelectionEdges()
|
||||
if edge2 == len(window.CurrentBuffer.Contents) {
|
||||
edge2 = len(window.CurrentBuffer.Contents) - 1
|
||||
}
|
||||
|
||||
str = str[:edge1] + str[edge2+1:]
|
||||
window.CurrentBuffer.Contents = str
|
||||
window.SetCursorPos(edge1)
|
||||
window.CurrentBuffer.Selection = nil
|
||||
}
|
||||
|
||||
index := window.CurrentBuffer.CursorPos
|
||||
|
||||
if index == len(str) {
|
||||
@@ -399,6 +476,20 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
} else if ev.Key() == tcell.KeyRune {
|
||||
if window.CursorMode == CursorModeBuffer {
|
||||
str := window.CurrentBuffer.Contents
|
||||
|
||||
// Remove selected text
|
||||
if window.CurrentBuffer.Selection != nil {
|
||||
edge1, edge2 := window.CurrentBuffer.GetSelectionEdges()
|
||||
if edge2 == len(window.CurrentBuffer.Contents) {
|
||||
edge2 = len(window.CurrentBuffer.Contents) - 1
|
||||
}
|
||||
|
||||
str = str[:edge1] + str[edge2+1:]
|
||||
window.CurrentBuffer.Contents = str
|
||||
window.SetCursorPos(edge1)
|
||||
window.CurrentBuffer.Selection = nil
|
||||
}
|
||||
|
||||
index := window.CurrentBuffer.CursorPos
|
||||
|
||||
if index == len(str) {
|
||||
@@ -426,29 +517,103 @@ func (window *Window) input(ev *tcell.EventKey) {
|
||||
|
||||
func (window *Window) mouseInput(ev *tcell.EventMouse) {
|
||||
mouseX, mouseY := ev.Position()
|
||||
bufferMouseX, bufferMouseY := window.AbsolutePosToBufferArea(mouseX, mouseY)
|
||||
|
||||
// Left click was pressed
|
||||
if ev.Buttons() == tcell.Button1 {
|
||||
// Get last click time
|
||||
lastClickTime := time.UnixMilli(lastClick)
|
||||
// Ensure click was in buffer area
|
||||
x1, y1, x2, y2 := window.GetTextAreaDimensions()
|
||||
if mouseX >= x1 && mouseY >= y1 && mouseX <= x2 && mouseY <= y2 {
|
||||
|
||||
currentX, currentY := window.GetCursorPos2D()
|
||||
bufferMouseX, bufferMouseY := window.AbsolutePosToCursorPos2D(mouseX, mouseY)
|
||||
if mouseHeld {
|
||||
// Add to selection
|
||||
if window.CurrentBuffer.Selection == nil {
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: window.CurrentBuffer.CursorPos,
|
||||
selectionEnd: window.CursorPos2DToCursorPos(bufferMouseX+window.CurrentBuffer.OffsetX, bufferMouseY+window.CurrentBuffer.OffsetY),
|
||||
selectionEnd: window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY),
|
||||
}
|
||||
|
||||
// Set last click time
|
||||
lastClick = time.Now().UnixMilli()
|
||||
|
||||
return
|
||||
} else {
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CursorPos2DToCursorPos(bufferMouseX+window.CurrentBuffer.OffsetX, bufferMouseY+window.CurrentBuffer.OffsetY)
|
||||
window.CurrentBuffer.Selection.selectionEnd = window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY)
|
||||
}
|
||||
// Prevent selecting dummy character at the end of the buffer
|
||||
if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) {
|
||||
window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1
|
||||
}
|
||||
} else if currentX == bufferMouseX && currentY == bufferMouseY && window.CurrentBuffer.CursorPos < len(window.CurrentBuffer.Contents) && time.Since(lastClickTime).Milliseconds() < 300 {
|
||||
selectedText := window.CurrentBuffer.GetSelectedText()
|
||||
if window.CurrentBuffer.Selection == nil || strings.HasSuffix(selectedText, "\n") {
|
||||
// Select word
|
||||
startOfWord := window.CurrentBuffer.CursorPos
|
||||
endOfWord := window.CurrentBuffer.CursorPos
|
||||
|
||||
// Find end of word
|
||||
for i := window.CurrentBuffer.CursorPos + 1; i < len(window.CurrentBuffer.Contents); i++ {
|
||||
currentRune := rune(window.CurrentBuffer.Contents[i])
|
||||
if unicode.IsLetter(currentRune) || unicode.IsDigit(currentRune) || currentRune == '_' {
|
||||
endOfWord++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find start of word
|
||||
for i := window.CurrentBuffer.CursorPos - 1; i >= 0; i-- {
|
||||
currentRune := rune(window.CurrentBuffer.Contents[i])
|
||||
if unicode.IsLetter(currentRune) || unicode.IsDigit(currentRune) || currentRune == '_' {
|
||||
startOfWord--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: startOfWord,
|
||||
selectionEnd: endOfWord,
|
||||
}
|
||||
} else {
|
||||
// Select line
|
||||
startOfLine := window.CurrentBuffer.CursorPos
|
||||
endOfLine := window.CurrentBuffer.CursorPos
|
||||
|
||||
// Find end of line
|
||||
for i := window.CurrentBuffer.CursorPos + 1; i < len(window.CurrentBuffer.Contents); i++ {
|
||||
currentLetter := window.CurrentBuffer.Contents[i]
|
||||
|
||||
endOfLine++
|
||||
if currentLetter == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find start of line
|
||||
for i := window.CurrentBuffer.CursorPos - 1; i >= 0; i-- {
|
||||
currentLetter := window.CurrentBuffer.Contents[i]
|
||||
if currentLetter != '\n' {
|
||||
startOfLine--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
window.CurrentBuffer.Selection = &Selection{
|
||||
selectionStart: startOfLine,
|
||||
selectionEnd: endOfLine,
|
||||
}
|
||||
}
|
||||
|
||||
// Set last click time
|
||||
lastClick = time.Now().UnixMilli()
|
||||
|
||||
return
|
||||
} else {
|
||||
// Clear selection
|
||||
if window.CurrentBuffer.Selection != nil {
|
||||
@@ -456,7 +621,10 @@ func (window *Window) mouseInput(ev *tcell.EventMouse) {
|
||||
}
|
||||
}
|
||||
// Move cursor
|
||||
window.SetCursorPos2D(bufferMouseX+window.CurrentBuffer.OffsetX, bufferMouseY+window.CurrentBuffer.OffsetY)
|
||||
window.SetCursorPos2D(bufferMouseX, bufferMouseY)
|
||||
|
||||
// Set last click time
|
||||
lastClick = time.Now().UnixMilli()
|
||||
}
|
||||
mouseHeld = true
|
||||
} else if ev.Buttons() == tcell.ButtonNone {
|
||||
@@ -531,12 +699,48 @@ func (window *Window) CursorPos2DToCursorPos(x, y int) int {
|
||||
return lines[y].charIndex + x
|
||||
}
|
||||
|
||||
func (window *Window) AbsolutePosToBufferArea(x, y int) (int, int) {
|
||||
func (window *Window) AbsolutePosToCursorPos2D(x, y int) (int, int) {
|
||||
x1, y1, _, _ := window.GetTextAreaDimensions()
|
||||
|
||||
x -= x1
|
||||
y -= y1
|
||||
|
||||
x += window.CurrentBuffer.OffsetX
|
||||
y += window.CurrentBuffer.OffsetY
|
||||
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
if y < 0 {
|
||||
y = 0
|
||||
}
|
||||
|
||||
split := strings.SplitAfter(window.CurrentBuffer.Contents+" ", "\n")
|
||||
|
||||
if y >= len(split) {
|
||||
y = len(split) - 1
|
||||
}
|
||||
line := split[y]
|
||||
|
||||
posInLine := make([]int, 0)
|
||||
for i, char := range []rune(line) {
|
||||
if char == '\t' {
|
||||
for j := 0; j < Config.TabIndentation; j++ {
|
||||
posInLine = append(posInLine, i)
|
||||
}
|
||||
} else {
|
||||
posInLine = append(posInLine, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(posInLine) == 0 {
|
||||
x = 0
|
||||
} else if x >= len(posInLine) {
|
||||
x = posInLine[len(posInLine)-1]
|
||||
} else {
|
||||
x = posInLine[x]
|
||||
}
|
||||
|
||||
return x, y
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user