// SPDX-License-Identifier: GPL-3.0-or-later package model // SpanKind is what a stretch of a configuration file is, for colouring it. type SpanKind int const ( SpanComment SpanKind = iota SpanString SpanHead // the symbol a form starts with SpanAction // a head that does something to a file SpanParen ) // Span is one stretch of text to colour, in character offsets - what a // GtkTextBuffer counts in, not bytes. type Span struct { From, To int Kind SpanKind } // actionHeads are the forms that act on a file rather than describe one. var actionHeads = map[string]bool{ "copy": true, "move": true, "rename": true, "delete": true, "stop": true, } // Spans reads a configuration and says what to colour: a ";" outside a // string comments out the rest of its line; a string runs to its closing // quote, a backslash escaping the next character; the first symbol after // "(" is the form's head. Nothing here knows about widgets, so the rules of // the little language stay testable. func Spans(text string) []Span { var out []Span runes := []rune(text) afterParen := false for i := 0; i < len(runes); i++ { switch c := runes[i]; { case c == ';': j := i for j < len(runes) && runes[j] != '\n' { j++ } out = append(out, Span{i, j, SpanComment}) i = j afterParen = false case c == '"': j := i + 1 for j < len(runes) { if runes[j] == '\\' { j += 2 continue } if runes[j] == '"' { j++ break } j++ } if j > len(runes) { j = len(runes) } out = append(out, Span{i, j, SpanString}) i = j - 1 afterParen = false case c == '(' || c == ')': out = append(out, Span{i, i + 1, SpanParen}) afterParen = c == '(' case c == ' ' || c == '\t' || c == '\n' || c == '\r': // whitespace between "(" and the head is allowed default: j := i for j < len(runes) && !isDelimiter(runes[j]) { j++ } if afterParen { kind := SpanHead if actionHeads[string(runes[i:j])] { kind = SpanAction } out = append(out, Span{i, j, kind}) } i = j - 1 afterParen = false } } return out } // isDelimiter reports whether a character ends a symbol. func isDelimiter(r rune) bool { switch r { case ' ', '\t', '\n', '\r', '(', ')', '"', ';': return true } return false }