Highlight Multiple Languages

Published: (December 1, 2025 at 09:13 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

The changes explained in this post can be found in the Kilo-go GitHub repository, in the multilingual branch.

Refactor: move the syntax struct to its own package

The EditorSyntax struct is extracted into its own package so that the editor can use the methods defined there.

syntax/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
}

// HLDB holds all supported syntaxes.
var HLDB = []EditorSyntax{
	GO_SYNTAX,
	// C_SYNTAX will be added later.
}

Updated references in the editor

editor/editor.go

type EditorConfig struct {
	// …
	syntax *syntax.EditorSyntax
}

editor/output.go

func (e *EditorConfig) editorDrawStatusBar(abuf *ab.AppendBuffer) {
	// …
	if e.syntax != nil {
		filetype = "[" + e.syntax.Filetype + "]"
	}
	// …
}

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
	// …
	if e.syntax.Flags&utils.HL_HIGHLIGHT_STRING == 2 {
		// handle strings
	}
	if e.syntax.Flags&utils.HL_HIGHLIGHT_NUMBER == 1 {
		// handle numbers
	}
	// …
}

Adding C/C++ support

Support for C/C++ is added by defining a new EditorSyntax value.

syntax/go.go (unchanged)

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,
}

syntax/c.go

package syntax

import "github.com/alcb1310/kilo-go/utils"

var C_HL_EXTENSIONS = []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",
	},
}

Update HLDB to include C syntax

var HLDB = []EditorSyntax{
	GO_SYNTAX,
	C_SYNTAX,
}

Bug fix in syntax selection

The syntax‑selection routine now iterates over all file‑match patterns for each language.

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

Instead of recompiling the binary for each new language, syntax definitions can be placed in the user’s configuration directory (${XDG_CONFIG}/kilo/highlight/) as TOML files. The editor loads them at startup.

${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 = "*/"

${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 = "*/"

Loading syntax definitions

func init() {
	// …
	syntax.LoadSyntax() // reads all *.toml files from ${XDG_CONFIG}/kilo/highlight/
}

With this approach, adding a new language only requires dropping a correctly‑formatted TOML file into the highlight directory—no changes to the editor’s source code are needed.

Back to Blog

Related posts

Read more »

Thoughts on Go vs. Rust vs. Zig

Article URL: https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/ Comments URL: https://news.ycombinator.com/item?id=46153466 Points: 11 Com...

The Secret Life of Go: Interfaces

The Power of Implicit Contracts Tuesday morning brought fog. Ethan descended to the archive carrying coffee and a small box of biscotti. Eleanor looked up. “...

how to use GVM (Go Version Manager)

Installing GVM To install GVM on your system, follow these steps. 1. Install required dependencies bash sudo apt-get install curl git mercurial make binutils b...