Compare commits

..
3 Commits
Author SHA1 Message Date
EnumDev b784bc0208 Show top menu buttons at the correct X position 2026-05-04 23:00:28 +03:00
EnumDev 999f1953f9 Add logs buffer 2026-05-04 22:46:21 +03:00
EnumDev e4462f9674 Add simple syntax highlighting 2026-05-04 21:33:35 +03:00
14 changed files with 432 additions and 61 deletions
+1 -1
View File
@@ -6,5 +6,5 @@ selected_style_fallback: "default-fallback" # Style for 8-color capable terminal
show_top_menu: true
show_line_index: true
extend_line_index: false # Extend line index to the bottom of the screen
buffer_info_message: "File: %f Cursor: (%x, %y, %p) Chars: %c"
buffer_info_message: "File: %f, Filetype: %t, Cursor: (%x, %y, %p), Chars: %c"
tab_indentation: 4 # Length of tab characters
+12
View File
@@ -19,3 +19,15 @@ colors:
message_bar_fg: "black" # Message bar text color
input_bar_bg: "245" # Input bar background color
input_bar_fg: "black" # Input bar text color
# Syntax highlighting
syntax_comment: "darkgray"
syntax_keyword: "lightgreen"
syntax_identifier: "yellow"
syntax_constant: "purple"
syntax_variable: "yellow"
syntax_string: "purple"
# Typer logs highlighting
syntax_info: "lightgreen"
syntax_warning: "gold"
syntax_error: "red"
+4
View File
@@ -19,3 +19,7 @@ colors:
message_bar_fg: "black" # Message bar text color
input_bar_bg: "white" # Input bar background color
input_bar_fg: "black" # Input bar text color
# Typer logs highlighting
syntax_info: "green"
syntax_warning: "yellow"
syntax_error: "red"
+14 -1
View File
@@ -5,6 +5,7 @@ style_type: "256-color"
# Colors
colors:
# Main 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
@@ -18,4 +19,16 @@ colors:
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
input_bar_fg: "white" # Input bar text color
# Syntax highlighting
syntax_comment: "gray"
syntax_keyword: "gold"
syntax_identifier: "purple"
syntax_constant: "teal"
syntax_variable: "purple"
syntax_string: "blue"
# Typer logs highlighting
syntax_info: "green"
syntax_warning: "yellow"
syntax_error: "red"
+12
View File
@@ -0,0 +1,12 @@
filetype: typer_logs
rules:
- type: info
regex: '^\[INFO\].*$'
multiline: true
- type: warning
regex: '^\[WARNING\].*$'
multiline: true
- type: error
regex: '^\[ERROR\].*$'
multiline: true
+15
View File
@@ -0,0 +1,15 @@
filetype: yaml
filenames: ".y[a]?ml$"
rules:
- type: string # Strings
regex: '(["''])(.*?)(["''])'
- type: comment
regex: "#.*"
- type: identifier # Keys
regex: '^[[:blank:]]*(-.)?([a-z0-9\._\-])+:'
multiline: true
- type: constant # Number values
regex: '\d+(\.\d+)?'
- type: constant # True/false values
regex: '\b(YES|yes|Y|y|ON|on|TRUE|True|true|NO|no|N|n|OFF|off|FALSE|False|false)\b'
+53
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"typer/runestring"
@@ -21,6 +22,8 @@ type Buffer struct {
Selection *Selection
canSave bool
canEdit bool
filetype string
filename string
}
@@ -55,14 +58,51 @@ func drawBuffer(window *Window) {
bufferX, bufferY, _, _ := window.GetTextAreaDimensions()
parsedSyntaxes, err := HighlightString(string(buffer.GetContentsAsString()), buffer.filetype)
if err != nil {
window.PrintMessage(fmt.Sprintf("Could not parse regular expression in '%s' syntax: %s", buffer.filetype, err), TYPER_MESSAGE_ERROR)
}
i := -1
for lineIndex, line := range buffer.Contents {
for runeIndex, r := range append(line, ' ') {
i++
drawPosition := Position{runeIndex, lineIndex}
if x-buffer.Offset.X >= bufferX && y-buffer.Offset.Y >= bufferY {
// Default style
style := tcell.StyleDefault.Background(CurrentStyle.BufferAreaBg).Foreground(CurrentStyle.BufferAreaFg)
// Check for syntax highlighting
for _, parsedSyntax := range parsedSyntaxes {
if i >= parsedSyntax.StartIndex && i < parsedSyntax.EndIndex {
switch parsedSyntax.Type {
case "comment":
style = style.Foreground(CurrentStyle.SyntaxComment)
case "keyword":
style = style.Foreground(CurrentStyle.SyntaxKeyword)
case "identifier":
style = style.Foreground(CurrentStyle.SyntaxIdentifier)
case "constant":
style = style.Foreground(CurrentStyle.SyntaxConstant)
case "variable":
style = style.Foreground(CurrentStyle.SyntaxVariable)
case "string":
style = style.Foreground(CurrentStyle.SyntaxString)
// Special types for typer logs
case "info":
style = style.Foreground(CurrentStyle.SyntaxInfo)
case "warning":
style = style.Foreground(CurrentStyle.SyntaxWarning)
case "error":
style = style.Foreground(CurrentStyle.SyntaxError)
}
break
}
}
// Change background if under cursor
if buffer.CursorPos.Equals(runeIndex, lineIndex) {
style = style.Background(CurrentStyle.BufferAreaSel)
@@ -135,6 +175,17 @@ func (buffer *Buffer) Load() error {
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
}
// Set buffer filetype
for _, syntax := range AvailableSyntaxes {
if syntax.Filenames == "" {
continue
}
if ok, _ := regexp.MatchString(syntax.Filenames, buffer.filename); ok {
buffer.filetype = syntax.Filetype
}
}
return nil
}
@@ -603,6 +654,7 @@ func CreateFileBuffer(filename string, openNonExistentFile bool) (*Buffer, error
Contents: make([]runestring.RuneString, 1),
CursorPos: Position{0, 0},
canSave: true,
canEdit: true,
filename: abs,
}
@@ -626,6 +678,7 @@ func CreateBuffer(bufferName string) (*Buffer, error) {
Contents: make([]runestring.RuneString, 1),
CursorPos: Position{0, 0},
canSave: true,
canEdit: true,
filename: "",
}
+91 -40
View File
@@ -29,7 +29,7 @@ func initCommands() {
selectionEnd: Position{len(window.CurrentBuffer.Contents) - 1, len(lastLine) - 1},
}
window.PrintMessage("Selected all text.")
window.PrintMessage("Selected all text", TYPER_MESSAGE_INFO)
},
}
@@ -44,9 +44,9 @@ func initCommands() {
// Send appropriate message and remove text depending on copying method
if copyingMethod == 0 {
window.PrintMessage("Copied line to clipboard.")
window.PrintMessage("Copied line to clipboard", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage("Copied selection to clipboard.")
window.PrintMessage("Copied selection to clipboard", TYPER_MESSAGE_INFO)
}
},
}
@@ -62,9 +62,9 @@ func initCommands() {
// Send appropriate message depending on copying method
if copyingMethod == 0 {
window.PrintMessage("Copied line to clipboard.")
window.PrintMessage("Copied line to clipboard", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage("Copied selection to clipboard. ")
window.PrintMessage("Copied selection to clipboard", TYPER_MESSAGE_INFO)
}
},
}
@@ -72,9 +72,14 @@ func initCommands() {
pasteCmd := Command{
cmd: "paste",
run: func(window *Window, args ...string) {
if !window.CurrentBuffer.canEdit {
window.PrintMessage(fmt.Sprintf("Buffer '%s' is read-only", window.CurrentBuffer.Name), TYPER_MESSAGE_WARNING)
return
}
if len(window.Clipboard) != 0 {
window.CurrentBuffer.PasteText(window, window.Clipboard)
window.PrintMessage("Pasted text to buffer. ")
window.PrintMessage("Pasted text to buffer", TYPER_MESSAGE_INFO)
}
},
}
@@ -83,7 +88,7 @@ func initCommands() {
cmd: "save",
run: func(window *Window, args ...string) {
if !window.CurrentBuffer.canSave {
window.PrintMessage("Cannot save buffer!")
window.PrintMessage("Cannot save buffer", TYPER_MESSAGE_ERROR)
return
}
@@ -100,7 +105,7 @@ func initCommands() {
input = <-inputChannel
if strings.TrimSpace(input) == "" {
window.PrintMessage("No save location was given!")
window.PrintMessage("No save location was given", TYPER_MESSAGE_ERROR)
return
}
@@ -108,12 +113,12 @@ func initCommands() {
err := window.CurrentBuffer.Save()
if err != nil {
window.PrintMessage(fmt.Sprintf("Could not save file: %s", err))
window.PrintMessage(fmt.Sprintf("Could not save file: %s", err), TYPER_MESSAGE_ERROR)
window.CurrentBuffer.filename = ""
return
}
window.PrintMessage("File saved.")
window.PrintMessage("File saved", TYPER_MESSAGE_INFO)
}()
},
autocomplete: func(window *Window, args ...string) []string {
@@ -133,16 +138,16 @@ func initCommands() {
}
if openBuffer := GetOpenFileBuffer(input); openBuffer != nil {
window.PrintMessage(fmt.Sprintf("File already open! Switching to buffer: %s", openBuffer.Name))
window.PrintMessage(fmt.Sprintf("File already open! Switching to buffer: %s", openBuffer.Name), TYPER_MESSAGE_INFO)
window.CurrentBuffer = openBuffer
} else {
newBuffer, err := CreateFileBuffer(input, false)
if err != nil {
window.PrintMessage(fmt.Sprintf("Could not open file: %s", err.Error()))
window.PrintMessage(fmt.Sprintf("Could not open file: %s", err.Error()), TYPER_MESSAGE_WARNING)
return
}
window.PrintMessage(fmt.Sprintf("Opening file at: %s", newBuffer.filename))
window.PrintMessage(fmt.Sprintf("Opening file at: %s", newBuffer.filename), TYPER_MESSAGE_INFO)
window.CurrentBuffer = newBuffer
}
}()
@@ -157,7 +162,7 @@ func initCommands() {
log.Fatalf("Could not reload buffer: %s", err)
}
window.PrintMessage("Buffer reloaded.")
window.PrintMessage("Buffer reloaded", TYPER_MESSAGE_INFO)
},
}
@@ -174,9 +179,9 @@ func initCommands() {
pos := window.CurrentBuffer.FindSubstring(input, window.CurrentBuffer.CursorPos)
if pos.X >= 0 && pos.Y >= 0 {
window.CurrentBuffer.CursorPos = pos
window.PrintMessage("Match found.")
window.PrintMessage("Match found", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(input)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(input)), TYPER_MESSAGE_WARNING)
}
return
@@ -193,9 +198,9 @@ func initCommands() {
pos := window.CurrentBuffer.FindSubstring(input, window.CurrentBuffer.CursorPos)
if pos.X >= 0 && pos.Y >= 0 {
window.CurrentBuffer.CursorPos = pos
window.PrintMessage("Match found.")
window.PrintMessage("Match found", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(input)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(input)), TYPER_MESSAGE_WARNING)
}
}()
},
@@ -215,9 +220,9 @@ func initCommands() {
pos := window.CurrentBuffer.FindAndReplaceSubstring(findStr, replaceStr, window.CurrentBuffer.CursorPos)
if pos.X >= 0 && pos.Y >= 0 {
window.CurrentBuffer.CursorPos = pos
window.PrintMessage("Match replaced successfully.")
window.PrintMessage("Match replaced successfully", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(findStr)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(findStr)), TYPER_MESSAGE_WARNING)
}
return
@@ -236,9 +241,9 @@ func initCommands() {
pos := window.CurrentBuffer.FindAndReplaceSubstring(findStr, replaceStr, window.CurrentBuffer.CursorPos)
if pos.X >= 0 && pos.Y >= 0 {
window.CurrentBuffer.CursorPos = pos
window.PrintMessage("Match replaced successfully.")
window.PrintMessage("Match replaced successfully", TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(findStr)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(findStr)), TYPER_MESSAGE_WARNING)
}
}()
},
@@ -257,9 +262,9 @@ func initCommands() {
replacements := window.CurrentBuffer.FindAndReplaceAll(findStr, replaceStr)
if replacements > 0 {
window.PrintMessage(fmt.Sprintf("Replaced all %d matches successfully.", replacements))
window.PrintMessage(fmt.Sprintf("Replaced all %d matches successfully", replacements), TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(findStr)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(findStr)), TYPER_MESSAGE_WARNING)
}
return
@@ -277,9 +282,9 @@ func initCommands() {
replacements := window.CurrentBuffer.FindAndReplaceAll(findStr, replaceStr)
if replacements > 0 {
window.PrintMessage(fmt.Sprintf("Replaced all %d matches successfully.", replacements))
window.PrintMessage(fmt.Sprintf("Replaced all %d matches successfully", replacements), TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer!", string(findStr)))
window.PrintMessage(fmt.Sprintf("'%s' not found in buffer", string(findStr)), TYPER_MESSAGE_WARNING)
}
}()
},
@@ -300,7 +305,7 @@ func initCommands() {
}
window.CurrentBuffer = Buffers[index]
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'", window.CurrentBuffer.Name), TYPER_MESSAGE_INFO)
},
}
@@ -319,7 +324,7 @@ func initCommands() {
}
window.CurrentBuffer = Buffers[index]
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'", window.CurrentBuffer.Name), TYPER_MESSAGE_INFO)
},
}
@@ -335,7 +340,7 @@ func initCommands() {
}
window.CursorMode = CursorModeBuffer
window.PrintMessage(fmt.Sprintf("New buffer created with the name '%s'.", window.CurrentBuffer.Name))
window.PrintMessage(fmt.Sprintf("New buffer created with the name '%s'", window.CurrentBuffer.Name), TYPER_MESSAGE_INFO)
},
}
@@ -354,7 +359,7 @@ func initCommands() {
window.CurrentBuffer = Buffers[bufferIndex]
}
window.CursorMode = CursorModeBuffer
window.PrintMessage("Buffer closed.")
window.PrintMessage("Buffer closed", TYPER_MESSAGE_INFO)
},
}
@@ -383,14 +388,14 @@ func initCommands() {
}
if _, ok := AvailableStyles[input]; !ok {
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input), TYPER_MESSAGE_ERROR)
return
}
if ok := SetCurrentStyle(window.screen, input); ok {
window.PrintMessage(fmt.Sprintf("Setting style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Setting style to '%s'", input), TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input), TYPER_MESSAGE_ERROR)
}
return
@@ -405,25 +410,70 @@ func initCommands() {
}
if _, ok := AvailableStyles[input]; !ok {
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input), TYPER_MESSAGE_ERROR)
return
}
if ok := SetCurrentStyle(window.screen, input); ok {
window.PrintMessage(fmt.Sprintf("Setting style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Setting style to '%s'", input), TYPER_MESSAGE_INFO)
} else {
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input))
window.PrintMessage(fmt.Sprintf("Could not set style to '%s'", input), TYPER_MESSAGE_ERROR)
}
}()
},
}
setFiletypeCmd := Command{
cmd: "set-filetype",
run: func(window *Window, args ...string) {
if len(args) >= 1 {
input := args[0]
if input == "" {
return
}
if strings.ToLower(input) == "none" {
window.CurrentBuffer.filetype = ""
window.PrintMessage("Setting filetype to 'none'", TYPER_MESSAGE_INFO)
return
} else if _, ok := AvailableSyntaxes[input]; !ok {
window.PrintMessage(fmt.Sprintf("Could not set filetype to '%s'", input), TYPER_MESSAGE_ERROR)
return
}
window.CurrentBuffer.filetype = input
window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input), TYPER_MESSAGE_INFO)
return
}
inputChannel := RequestInput(window, "Filetype to switch to:", "")
go func() {
input := <-inputChannel
if input == "" {
return
}
if _, ok := AvailableSyntaxes[input]; !ok {
window.PrintMessage(fmt.Sprintf("Could not set filetype to '%s'", input), TYPER_MESSAGE_ERROR)
return
}
window.CurrentBuffer.filetype = input
window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input), TYPER_MESSAGE_INFO)
}()
},
}
menuFileCmd := Command{
cmd: "menu-file",
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "File" {
button.Action(window)
button.Action(window, &button)
break
}
}
@@ -435,7 +485,7 @@ func initCommands() {
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "Edit" {
button.Action(window)
button.Action(window, &button)
break
}
}
@@ -447,7 +497,7 @@ func initCommands() {
run: func(window *Window, args ...string) {
for _, button := range TopMenuButtons {
if button.Name == "Buffers" {
button.Action(window)
button.Action(window, &button)
break
}
}
@@ -521,6 +571,7 @@ func initCommands() {
commands["toggle-top-bar"] = &toggleTopBar
commands["toggle-line-index"] = &toggleLineIndex
commands["set-style"] = &setStyleCmd
commands["set-filetype"] = &setFiletypeCmd
commands["menu-file"] = &menuFileCmd
commands["menu-edit"] = &menuEditCmd
commands["menu-buffers"] = &menuBuffersCmd
@@ -533,7 +584,7 @@ func RunCommand(window *Window, cmd string, args ...string) bool {
command.run(window, args...)
return true
} else {
window.PrintMessage(fmt.Sprintf("Could not find command '%s'!", cmd))
window.PrintMessage(fmt.Sprintf("Could not find command '%s'", cmd), TYPER_MESSAGE_ERROR)
return false
}
}
+109
View File
@@ -0,0 +1,109 @@
package main
import (
"log"
"os"
"path/filepath"
"regexp"
"gopkg.in/yaml.v3"
)
type SyntaxRule struct {
Type string `yaml:"type"`
Regex string `yaml:"regex"`
Multiline bool `yaml:"multiline"`
}
type Syntax struct {
Filetype string `yaml:"filetype"`
Filenames string `yaml:"filenames"`
Rules []SyntaxRule `yaml:"rules"`
}
type ParsedSyntax struct {
StartIndex int
EndIndex int
Type string
}
var AvailableSyntaxes map[string]Syntax = make(map[string]Syntax)
func ReadSyntaxHighlighters() {
// Get syntax directory path
syntaxDirPath := GetConfigPath("syntax")
// Ensure directory exists at path
if stat, err := os.Stat(syntaxDirPath); syntaxDirPath == "" || err != nil || !stat.IsDir() {
return
}
// Get directory entries
entries, err := os.ReadDir(syntaxDirPath)
if err != nil {
log.Fatalf("Could not read syntax directory: %s", err)
}
// Read entries in directory
for _, entry := range entries {
entryPath := filepath.Join(syntaxDirPath, entry.Name())
data, err := os.ReadFile(entryPath)
if err != nil {
log.Fatalf("Could not read syntax file (%s): %s", entryPath, err)
}
syntax := Syntax{}
err = yaml.Unmarshal(data, &syntax)
if err != nil {
log.Fatalf("Could not read syntax file (%s): %s", entryPath, err)
}
if _, ok := AvailableSyntaxes[syntax.Filetype]; !ok {
AvailableSyntaxes[syntax.Filetype] = syntax
}
}
}
func HighlightString(s string, filetype string) (parsedSyntaxes []ParsedSyntax, err error) {
// Get syntax for filetype
syntax, ok := AvailableSyntaxes[filetype]
if !ok {
return nil, nil
}
for _, rule := range syntax.Rules {
regex := rule.Regex
if rule.Multiline {
regex = "(?m)" + rule.Regex
}
r, err := regexp.Compile(regex)
if err != nil {
return nil, err
}
matches := r.FindAllStringIndex(s, -1)
for _, match := range matches {
skip := false
for _, parsedSyntax := range parsedSyntaxes {
if (match[0] >= parsedSyntax.StartIndex && match[0] < parsedSyntax.EndIndex) || (match[1] >= parsedSyntax.StartIndex && match[1] < parsedSyntax.EndIndex) {
skip = true
}
}
if skip {
continue
}
parsedSyntax := ParsedSyntax{
StartIndex: match[0],
EndIndex: match[1],
Type: rule.Type,
}
parsedSyntaxes = append(parsedSyntaxes, parsedSyntax)
}
}
return parsedSyntaxes, nil
}
+12 -1
View File
@@ -23,6 +23,9 @@ func main() {
// Read styles directory
readStyles()
// Read syntax directory
ReadSyntaxHighlighters()
// Initialize commands
initCommands()
@@ -35,7 +38,7 @@ func main() {
for i, file := range flag.Args() {
b, err := CreateFileBuffer(file, true)
if err != nil {
window.PrintMessage("Could not open file: " + file)
window.PrintMessage("Could not open file: "+file, TYPER_MESSAGE_ERROR)
continue
}
@@ -46,6 +49,14 @@ func main() {
}
}
// Create logs buffer
logsBuffer, err := CreateBuffer("Logs")
if err != nil {
log.Fatalf("Could not create logs buffer")
}
logsBuffer.filetype = "typer_logs"
logsBuffer.canEdit = false
for !window.closed {
window.Draw()
window.ProcessEvents()
+57 -7
View File
@@ -1,20 +1,60 @@
package main
import (
"slices"
"time"
"typer/runestring"
"github.com/gdamore/tcell/v2"
)
type TyperMessageUrgency uint
const (
TYPER_MESSAGE_INFO TyperMessageUrgency = iota
TYPER_MESSAGE_WARNING
TYPER_MESSAGE_ERROR
)
type TyperMessage struct {
timestamp int64
message string
Timestamp int64
Urgency TyperMessageUrgency
Message string
}
var messageLog = make([]TyperMessage, 0)
var lastMessage *TyperMessage
func (window *Window) PrintMessage(message string) {
messageLog = append(messageLog, TyperMessage{timestamp: time.Now().UnixMilli(), message: message})
func (window *Window) PrintMessage(message string, urgency TyperMessageUrgency) {
lastMessage = &TyperMessage{Timestamp: time.Now().UnixMilli(), Message: message, Urgency: urgency}
logsBuffer := GetBufferByName("Logs")
if logsBuffer != nil {
messageToPrint := ""
switch lastMessage.Urgency {
case TYPER_MESSAGE_INFO:
messageToPrint = "[INFO] "
case TYPER_MESSAGE_WARNING:
messageToPrint = "[WARNING] "
case TYPER_MESSAGE_ERROR:
messageToPrint = "[ERROR] "
default:
messageToPrint = "[???] "
}
messageToPrint += "[" + time.UnixMilli(lastMessage.Timestamp).Format("15:04:05") + "] "
messageToPrint += lastMessage.Message + "\n"
if len(logsBuffer.Contents) >= 1000 {
logsBuffer.Contents = slices.Delete(logsBuffer.Contents, 0, len(logsBuffer.Contents)-999)
}
logsBuffer.CursorPos = Position{
X: len(logsBuffer.Contents[len(logsBuffer.Contents)-1]),
Y: len(logsBuffer.Contents) - 1,
}
logsBuffer.WriteString(runestring.RuneString(messageToPrint))
}
err := window.screen.PostEvent(tcell.NewEventInterrupt(nil))
if err != nil {
@@ -39,8 +79,18 @@ func drawMessageBar(window *Window) {
sizeX, sizeY := screen.Size()
messageToPrint := ""
if len(messageLog) > 0 && time.Since(time.UnixMilli(messageLog[len(messageLog)-1].timestamp)).Seconds() < 5 {
messageToPrint = messageLog[len(messageLog)-1].message
if lastMessage != nil && time.Since(time.UnixMilli(lastMessage.Timestamp)).Seconds() < 5 {
switch lastMessage.Urgency {
case TYPER_MESSAGE_INFO:
messageToPrint = "[INFO] "
case TYPER_MESSAGE_WARNING:
messageToPrint = "[WARNING] "
case TYPER_MESSAGE_ERROR:
messageToPrint = "[ERROR] "
default:
messageToPrint = "[???] "
}
messageToPrint += lastMessage.Message
}
for x := 0; x < sizeX; x++ {
+13 -1
View File
@@ -20,7 +20,7 @@ type TyperStyle struct {
Description string
StyleType string
// Colors
// Main Colors
BufferAreaBg tcell.Color `name:"buffer_area_bg"`
BufferAreaFg tcell.Color `name:"buffer_area_fg"`
BufferAreaSel tcell.Color `name:"buffer_area_sel"`
@@ -35,6 +35,18 @@ type TyperStyle struct {
MessageBarFg tcell.Color `name:"message_bar_fg"`
InputBarBg tcell.Color `name:"input_bar_bg"`
InputBarFg tcell.Color `name:"input_bar_fg"`
// Syntax highlighting
SyntaxComment tcell.Color `name:"syntax_comment"`
SyntaxKeyword tcell.Color `name:"syntax_keyword"`
SyntaxIdentifier tcell.Color `name:"syntax_identifier"`
SyntaxConstant tcell.Color `name:"syntax_constant"`
SyntaxVariable tcell.Color `name:"syntax_variable"`
SyntaxString tcell.Color `name:"syntax_string"`
// Typer logs highlighting
SyntaxInfo tcell.Color `name:"syntax_info"`
SyntaxWarning tcell.Color `name:"syntax_warning"`
SyntaxError tcell.Color `name:"syntax_error"`
}
type typerStyleYaml struct {
+17 -9
View File
@@ -11,7 +11,8 @@ import (
type TopMenuButton struct {
Name string
Action func(w *Window)
PosX int
Action func(w *Window, b *TopMenuButton)
}
var TopMenuButtons = make([]TopMenuButton, 0)
@@ -20,7 +21,7 @@ func initTopMenu() {
// Buttons
fileButton := TopMenuButton{
Name: "File",
Action: func(window *Window) {
Action: func(window *Window, button *TopMenuButton) {
ClearDropdowns()
y := 0
@@ -28,7 +29,7 @@ func initTopMenu() {
y++
}
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, 0, y, 0, func(i int) {
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, button.PosX, y, 0, func(i int) {
switch i {
case 0:
RunCommand(window, "new-buffer")
@@ -49,7 +50,7 @@ func initTopMenu() {
}
EditButton := TopMenuButton{
Name: "Edit",
Action: func(window *Window) {
Action: func(window *Window, button *TopMenuButton) {
ClearDropdowns()
y := 0
@@ -57,7 +58,7 @@ func initTopMenu() {
y++
}
d := CreateDropdownMenu([]string{"Cut", "Copy", "Paste"}, 0, y, 0, func(i int) {
d := CreateDropdownMenu([]string{"Cut", "Copy", "Paste"}, button.PosX, y, 0, func(i int) {
switch i {
case 0:
RunCommand(window, "cut")
@@ -75,7 +76,7 @@ func initTopMenu() {
}
Buffers := TopMenuButton{
Name: "Buffers",
Action: func(window *Window) {
Action: func(window *Window, button *TopMenuButton) {
ClearDropdowns()
y := 0
@@ -92,9 +93,9 @@ func initTopMenu() {
}
}
d := CreateDropdownMenu(buffersSlice, 0, y, 0, func(i int) {
d := CreateDropdownMenu(buffersSlice, button.PosX, y, 0, func(i int) {
window.CurrentBuffer = Buffers[i]
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'.", window.CurrentBuffer.Name))
window.PrintMessage(fmt.Sprintf("Set current buffer to '%s'", window.CurrentBuffer.Name), TYPER_MESSAGE_INFO)
ClearDropdowns()
window.CursorMode = CursorModeBuffer
})
@@ -119,8 +120,9 @@ func drawTopMenu(window *Window) {
}
currentX := 1
for _, button := range TopMenuButtons {
for i, button := range TopMenuButtons {
drawText(screen, currentX, 0, currentX+len(button.Name), 0, topMenuStyle, button.Name)
TopMenuButtons[i].PosX = currentX
currentX += len(button.Name) + 1
}
@@ -134,6 +136,7 @@ func drawTopMenu(window *Window) {
func getBufferInfoMsg(window *Window) string {
pathToFile := "Not set"
filename := "Not set"
filetype := "Not set"
if window.CurrentBuffer.filename != "" {
pathToFile = window.CurrentBuffer.filename
}
@@ -141,6 +144,10 @@ func getBufferInfoMsg(window *Window) string {
filename = filepath.Base(window.CurrentBuffer.filename)
}
if window.CurrentBuffer.filetype != "" {
filetype = window.CurrentBuffer.filetype
}
contents := window.CurrentBuffer.GetContentsAsString()
chars := len(contents)
words := len(strings.Fields(string(contents)))
@@ -150,6 +157,7 @@ func getBufferInfoMsg(window *Window) string {
ret = strings.ReplaceAll(ret, "\n", " ")
ret = strings.ReplaceAll(ret, "%F", pathToFile)
ret = strings.ReplaceAll(ret, "%f", filename)
ret = strings.ReplaceAll(ret, "%t", filetype)
ret = strings.ReplaceAll(ret, "%x", strconv.Itoa(window.CurrentBuffer.CursorPos.X+1))
ret = strings.ReplaceAll(ret, "%y", strconv.Itoa(window.CurrentBuffer.CursorPos.Y+1))
ret = strings.ReplaceAll(ret, "%p", strconv.Itoa(window.CurrentBuffer.PositionToAbsolutePosition(window.CurrentBuffer.CursorPos)+1))
+22 -1
View File
@@ -1,6 +1,7 @@
package main
import (
"fmt"
"log"
"slices"
"strconv"
@@ -86,7 +87,7 @@ func CreateWindow() (*Window, error) {
if ok := SetCurrentStyle(screen, Config.FallbackStyle); !ok {
// Use hard-coded fallback style
screen.SetStyle(tcell.StyleDefault.Foreground(CurrentStyle.BufferAreaFg).Background(CurrentStyle.BufferAreaBg))
window.PrintMessage("Could not set style either to selected one nor to fallback one!")
window.PrintMessage("Could not set style either to selected one nor to fallback one", TYPER_MESSAGE_ERROR)
}
}
@@ -396,6 +397,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) {
// Typing
if ev.Key() == tcell.KeyBackspace || ev.Key() == tcell.KeyBackspace2 {
if window.CursorMode == CursorModeBuffer {
if !window.CurrentBuffer.canEdit {
window.PrintMessage(fmt.Sprintf("Buffer '%s' is read-only", window.CurrentBuffer.Name), TYPER_MESSAGE_WARNING)
return
}
if window.CurrentBuffer.Selection != nil {
window.CurrentBuffer.CutText(window)
} else {
@@ -413,6 +419,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) {
}
} else if ev.Key() == tcell.KeyTab {
if window.CursorMode == CursorModeBuffer {
if !window.CurrentBuffer.canEdit {
window.PrintMessage(fmt.Sprintf("Buffer '%s' is read-only", window.CurrentBuffer.Name), TYPER_MESSAGE_WARNING)
return
}
// Remove selected text
if window.CurrentBuffer.Selection != nil {
window.CurrentBuffer.CutText(window)
@@ -422,6 +433,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) {
}
} else if ev.Key() == tcell.KeyEnter {
if window.CursorMode == CursorModeBuffer {
if !window.CurrentBuffer.canEdit {
window.PrintMessage(fmt.Sprintf("Buffer '%s' is read-only", window.CurrentBuffer.Name), TYPER_MESSAGE_WARNING)
return
}
// Remove selected text
if window.CurrentBuffer.Selection != nil {
window.CurrentBuffer.CutText(window)
@@ -441,6 +457,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) {
}
} else if ev.Key() == tcell.KeyRune {
if window.CursorMode == CursorModeBuffer {
if !window.CurrentBuffer.canEdit {
window.PrintMessage(fmt.Sprintf("Buffer '%s' is read-only", window.CurrentBuffer.Name), TYPER_MESSAGE_WARNING)
return
}
// Remove selected text
if window.CurrentBuffer.Selection != nil {
window.CurrentBuffer.CutText(window)