diff --git a/config/styles/classic.yml b/config/styles/classic.yml index aa02fdf..7882263 100644 --- a/config/styles/classic.yml +++ b/config/styles/classic.yml @@ -27,3 +27,7 @@ colors: syntax_constant: "purple" syntax_variable: "yellow" syntax_string: "purple" + # Typer logs highlighting + syntax_info: "lightgreen" + syntax_warning: "gold" + syntax_error: "red" diff --git a/config/styles/default-fallback.yml b/config/styles/default-fallback.yml index 7a62322..4f7f9ab 100644 --- a/config/styles/default-fallback.yml +++ b/config/styles/default-fallback.yml @@ -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" diff --git a/config/styles/default.yml b/config/styles/default.yml index 9c6009d..8521544 100644 --- a/config/styles/default.yml +++ b/config/styles/default.yml @@ -28,3 +28,7 @@ colors: syntax_constant: "teal" syntax_variable: "purple" syntax_string: "blue" + # Typer logs highlighting + syntax_info: "green" + syntax_warning: "yellow" + syntax_error: "red" diff --git a/config/syntax/typer_logs.yml b/config/syntax/typer_logs.yml new file mode 100644 index 0000000..9b07deb --- /dev/null +++ b/config/syntax/typer_logs.yml @@ -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 diff --git a/src/buffer.go b/src/buffer.go index 7e70468..ce52af0 100644 --- a/src/buffer.go +++ b/src/buffer.go @@ -22,6 +22,7 @@ type Buffer struct { Selection *Selection canSave bool + canEdit bool filetype string filename string } @@ -59,7 +60,7 @@ func drawBuffer(window *Window) { 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)) + window.PrintMessage(fmt.Sprintf("Could not parse regular expression in '%s' syntax: %s", buffer.filetype, err), TYPER_MESSAGE_ERROR) } i := -1 @@ -88,6 +89,14 @@ func drawBuffer(window *Window) { 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 @@ -168,6 +177,10 @@ func (buffer *Buffer) Load() error { // Set buffer filetype for _, syntax := range AvailableSyntaxes { + if syntax.Filenames == "" { + continue + } + if ok, _ := regexp.MatchString(syntax.Filenames, buffer.filename); ok { buffer.filetype = syntax.Filetype } @@ -641,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, } @@ -664,6 +678,7 @@ func CreateBuffer(bufferName string) (*Buffer, error) { Contents: make([]runestring.RuneString, 1), CursorPos: Position{0, 0}, canSave: true, + canEdit: true, filename: "", } diff --git a/src/command.go b/src/command.go index 30ca9e5..80979ca 100644 --- a/src/command.go +++ b/src/command.go @@ -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,14 +410,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) } }() }, @@ -430,15 +435,15 @@ func initCommands() { if strings.ToLower(input) == "none" { window.CurrentBuffer.filetype = "" - window.PrintMessage("Setting filetype to 'none'") + 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)) + 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)) + window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input), TYPER_MESSAGE_INFO) return } @@ -452,12 +457,12 @@ func initCommands() { } if _, ok := AvailableSyntaxes[input]; !ok { - window.PrintMessage(fmt.Sprintf("Could not set filetype to '%s'", input)) + 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)) + window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input), TYPER_MESSAGE_INFO) }() }, @@ -579,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 } } diff --git a/src/main.go b/src/main.go index 11b3429..c3199e3 100644 --- a/src/main.go +++ b/src/main.go @@ -38,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 } @@ -49,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() diff --git a/src/message_bar.go b/src/message_bar.go index aa16e97..1042cf0 100644 --- a/src/message_bar.go +++ b/src/message_bar.go @@ -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++ { diff --git a/src/style.go b/src/style.go index 03d1244..a8ea9ac 100644 --- a/src/style.go +++ b/src/style.go @@ -43,6 +43,10 @@ type TyperStyle struct { 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 { diff --git a/src/top_menu.go b/src/top_menu.go index a24fc5f..46dd806 100644 --- a/src/top_menu.go +++ b/src/top_menu.go @@ -94,7 +94,7 @@ func initTopMenu() { d := CreateDropdownMenu(buffersSlice, 0, 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 }) diff --git a/src/window.go b/src/window.go index 3eedd1f..d45c765 100644 --- a/src/window.go +++ b/src/window.go @@ -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)