mirror of
https://github.com/EnumeratedDev/typer.git
synced 2026-09-16 05:36:10 +00:00
Add simple syntax highlighting
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -23,6 +23,9 @@ func main() {
|
||||
// Read styles directory
|
||||
readStyles()
|
||||
|
||||
// Read syntax directory
|
||||
ReadSyntaxHighlighters()
|
||||
|
||||
// Initialize commands
|
||||
initCommands()
|
||||
|
||||
|
||||
+9
-1
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user