The changes explained in this post can be found in the Kilo-go Github repository, in the multilingual branch.
At the moment, we have syntax highlighting but only for Go, and we want our editor to be able to highlight multiple languages. What do we want to achieve:
- Highlight multiple languages
- Multiple language support
- Add new languages without editing the code
Refactor: move the syntax struct to its own package
First we will be moving the syntax struct to its own package so we have the methods we will be creating in it
File: sytnax/syntax.go
package syntax
import "github.com/alcb1310/kilo-go/utils"
var GO_HL_EXTENSIONS = []string{".go"}
var GO_HL_KEYWORDS = []string{"package", "import", "func", "type", "var", "const", "if", "else",
"switch", "case", "default", "for", "range", "goto", "continue", "select", "return", "break",
}
var GO_HL_TYPES = []string{"bool", "byte", "error", "float32", "float64", "int", "int16", "int32",
"int64", "int8", "rune", "string", "uint", "uint16", "uint32", "uint64", "uint8",
}
var GO_SYNTAX = EditorSyntax{
Filetype: "go",
Filematch: GO_HL_EXTENSIONS,
Flags: utils.HL_HIGHLIGHT_NUMBER | utils.HL_HIGHLIGHT_STRING,
SingleLineComment: "//",
MultiLineCommentStart: "/*",
MultiLineCommentEnd: "*/",
Keywords: GO_HL_KEYWORDS,
Types: GO_HL_TYPES,
}
type EditorSyntax struct {
Filetype string
Filematch []string
Flags uint
SingleLineComment string
MultiLineCommentStart string
MultiLineCommentEnd string
Keywords []string
Types []string
}
var HLDB = []EditorSyntax{
GO_SYNTAX,
C_SYNTAX,
}
File: editor/editor.go
type EditorConfig struct {
...
syntax *syntax.EditorSyntax
}
File: editor/output.go
func (e *EditorConfig) editorDrawStatusBar(abuf *ab.AppendBuffer) {
...
if e.syntax != nil {
filetype = "[" + e.syntax.Filetype + "]"
}
...
}
File: editor/syntax.go
func (e *EditorConfig) editorUpdateSyntax(row *EditorRow) {
...
scs := e.syntax.SingleLineComment
mcs := e.syntax.MultiLineCommentStart
mce := e.syntax.MultiLineCommentEnd
keywords := e.syntax.Keywords
types := e.syntax.Types
...
i := 0
for i < len(row.render) {
...
if e.syntax.Flags&utils.HL_HIGHLIGHT_STRING == 2 {
...
}
if e.syntax.Flags&utils.HL_HIGHLIGHT_NUMBER == 1 {
...
}
...
}
...
}
func (e *EditorConfig) editorSelectSyntaxHighlight() {
...
for i, s := range syntax.HLDB {
isExt := s.Filematch[i][0] == '.'
if (isExt && ext == s.Filematch[i]) ||
(!isExt && strings.Contains(ext, s.Filematch[i])) {
...
}
}
}
Adding C/C++ support
Now the next step is to add support for another language, we will add C/C++
File: syntax/syntax.go
package syntax
type EditorSyntax struct {
Filetype string
Filematch []string
Flags uint
SingleLineComment string
MultiLineCommentStart string
MultiLineCommentEnd string
Keywords []string
Types []string
}
var HLDB = []EditorSyntax{
GO_SYNTAX,
C_SYNTAX,
}
File: syntax/go.go
package syntax
import "github.com/alcb1310/kilo-go/utils"
var GO_HL_EXTENSIONS = []string{".go"}
var GO_HL_KEYWORDS = []string{"package", "import", "func", "type", "var", "const", "if", "else",
"switch", "case", "default", "for", "range", "goto", "continue", "select", "return", "break",
}
var GO_HL_TYPES = []string{"bool", "byte", "error", "float32", "float64", "int", "int16", "int32",
"int64", "int8", "rune", "string", "uint", "uint16", "uint32", "uint64", "uint8",
}
var GO_SYNTAX = EditorSyntax{
Filetype: "go",
Filematch: GO_HL_EXTENSIONS,
Flags: utils.HL_HIGHLIGHT_NUMBER | utils.HL_HIGHLIGHT_STRING,
SingleLineComment: "//",
MultiLineCommentStart: "/*",
MultiLineCommentEnd: "*/",
Keywords: GO_HL_KEYWORDS,
Types: GO_HL_TYPES,
}
File: syntax/c.go
package syntax
import "github.com/alcb1310/kilo-go/utils"
var C_HL_EXTENSIONS []string = []string{".c", ".h", ".cpp"}
var C_SYNTAX = EditorSyntax{
Filetype: "c",
Filematch: C_HL_EXTENSIONS,
Flags: utils.HL_HIGHLIGHT_NUMBER | utils.HL_HIGHLIGHT_STRING,
SingleLineComment: "//",
MultiLineCommentStart: "/*",
MultiLineCommentEnd: "*/",
Keywords: []string{"switch", "if", "while", "for", "break", "continue", "return", "else",
"struct", "union", "typedef", "static", "enum", "class", "case", "#include", "#define", "#ifndef", "#ifdef", "#endif", "#else"},
Types: []string{"int", "long", "double", "float", "char", "unsigned", "signed",
"void"},
}
After testing we can see that there is a bug that we need to address, that's why we need to update:
File: editor/syntax.go
func (e *EditorConfig) editorSelectSyntaxHighlight() {
...
for _, s := range syntax.HLDB {
for j := range s.Filematch {
isExt := s.Filematch[j][0] == '.'
if (isExt && ext == s.Filematch[j]) ||
(!isExt && strings.Contains(ext, s.Filematch[j])) {
e.syntax = &s
for _, row := range e.rows {
e.editorUpdateSyntax(&row)
}
return
}
}
}
}
Multilingual support without changing the editor
Finally we have multilingual support, but what happens if we need to add another language, well, we will need to add a new file in the syntax package for that language with the fields required and recompile the application. I'm sure we can do better, in the editor's config directory we can create a highlight directory where we can add files with the required information, so lets do that
File: ${XDG_CONFIG}/kilo/highlight/go.toml
number=true
string=true
filetype="GO"
extensions=[".go"]
keywords=[ "package", "import", "func", "type", "var", "const", "if", "else",
"switch", "case", "default", "for", "range", "goto", "continue", "select", "return", "break",
]
types=[ "bool", "byte", "error", "float32", "float64", "int", "int16", "int32", "nil",
"int64", "int8", "rune", "string", "uint", "uint16", "uint32", "uint64", "uint8",
]
slc="//"
mlcs="/*"
mlce="*/"
File: ${XDG_CONFIG}/kilo/highlight/c.toml
number=true
string=true
filetype="C/CPP"
extensions=[".c", ".h", ".cpp"]
keywords=["switch", "if", "while", "for", "break", "continue", "return", "else",
"struct", "union", "typedef", "static", "enum", "class", "case", "#include", "#define", "#ifndef", "#ifdef", "#endif", "#else"
]
types=["int", "long", "double", "float", "char", "unsigned", "signed",
"void", "NULL"]
slc="//"
mlcs="/*"
mlce="*/"
File: main.go
func init() {
...
syntax.LoadSyntax()
}
File: syntax/syntax.go
package syntax
import (
"fmt"
"os"
"path"
"github.com/BurntSushi/toml"
"github.com/alcb1310/kilo-go/utils"
)
type EditorSyntax struct {
Filetype string `toml:"filetype"`
Filematch []string `toml:"extensions"`
Flags uint
SingleLineComment string `toml:"slc"`
MultiLineCommentStart string `toml:"mlcs"`
MultiLineCommentEnd string `toml:"mlce"`
Keywords []string
Types []string
NumberFlag bool `toml:"number"`
StringFlag bool `toml:"string"`
}
var HLDB = []EditorSyntax{}
func LoadSyntax() error {
dir, err := os.UserConfigDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\r\n", err)
return err
}
tomlDir := path.Join(path.Join(dir, "kilo"), "highlight")
_, err = os.Stat(tomlDir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
fmt.Fprintf(os.Stderr, "Error: %s\r\n", err)
return err
}
files, err := os.ReadDir(tomlDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\r\n", err)
return err
}
for _, file := range files {
s := EditorSyntax{}
if file.IsDir() {
continue
}
_, err := toml.DecodeFile(path.Join(tomlDir, file.Name()), &s)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\r\n", err)
return err
}
if s.NumberFlag {
s.Flags |= utils.HL_HIGHLIGHT_NUMBER
}
if s.StringFlag {
s.Flags |= utils.HL_HIGHLIGHT_STRING
}
HLDB = append(HLDB, s)
}
return nil
}
Top comments (0)