Add simple syntax highlighting

This commit is contained in:
2026-05-04 21:33:35 +03:00
parent ef401b5aaa
commit e4462f9674
10 changed files with 245 additions and 3 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
+8
View File
@@ -19,3 +19,11 @@ 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"
+10 -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,12 @@ 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"
+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'
+38
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"typer/runestring"
@@ -21,6 +22,7 @@ type Buffer struct {
Selection *Selection
canSave bool
filetype string
filename string
}
@@ -55,14 +57,43 @@ 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))
}
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)
}
break
}
}
// Change background if under cursor
if buffer.CursorPos.Equals(runeIndex, lineIndex) {
style = style.Background(CurrentStyle.BufferAreaSel)
@@ -135,6 +166,13 @@ func (buffer *Buffer) Load() error {
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
}
// Set buffer filetype
for _, syntax := range AvailableSyntaxes {
if ok, _ := regexp.MatchString(syntax.Filenames, buffer.filename); ok {
buffer.filetype = syntax.Filetype
}
}
return nil
}
+46
View File
@@ -418,6 +418,51 @@ func initCommands() {
},
}
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'")
return
} else if _, ok := AvailableSyntaxes[input]; !ok {
window.PrintMessage(fmt.Sprintf("Could not set filetype to '%s'", input))
return
}
window.CurrentBuffer.filetype = input
window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input))
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))
return
}
window.CurrentBuffer.filetype = input
window.PrintMessage(fmt.Sprintf("Setting filetype to '%s'", input))
}()
},
}
menuFileCmd := Command{
cmd: "menu-file",
run: func(window *Window, args ...string) {
@@ -521,6 +566,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
+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
}
+3
View File
@@ -23,6 +23,9 @@ func main() {
// Read styles directory
readStyles()
// Read syntax directory
ReadSyntaxHighlighters()
// Initialize commands
initCommands()
+9 -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,14 @@ 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"`
}
type typerStyleYaml struct {
+6
View File
@@ -134,6 +134,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 +142,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 +155,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))