main bcc9a45

parse.go RawHistoryFolder

1package css
2
3import (
4	"strings"
5)
6
7type Diagnostic struct {
8	Line, Col int
9	Message   string
10}
11type Declaration struct {
12	Property, Value string
13	Important       bool
14	Line, Col       int
15}
16type Rule struct {
17	Selectors    []Selector
18	Declarations []Declaration
19}
20type Sheet struct {
21	Rules       []Rule
22	Diagnostics []Diagnostic
23}
24
25func Parse(src string) Sheet {
26	ts := Tokenize(src)
27	sheet := Sheet{}
28	diag := func(t Token, msg string) {
29		sheet.Diagnostics = append(sheet.Diagnostics, Diagnostic{t.Line, t.Col, msg})
30	}
31	for i := 0; i < len(ts); {
32		for i < len(ts) && ts[i].Kind == "space" {
33			i++
34		}
35		if i >= len(ts) {
36			break
37		}
38		start := i
39		for i < len(ts) && ts[i].Kind != "{" && ts[i].Kind != "}" && ts[i].Kind != ";" {
40			i++
41		}
42		if i >= len(ts) {
43			diag(ts[start], "malformed rule")
44			break
45		}
46		if ts[i].Kind != "{" {
47			diag(ts[start], "malformed rule")
48			i++
49			continue
50		}
51		sels, ok := parseSelectors(ts[start:i])
52		i++
53		depth := 1
54		body := i
55		for i < len(ts) && depth > 0 {
56			switch ts[i].Kind {
57			case "{":
58				depth++
59			case "}":
60				depth--
61			}
62			i++
63		}
64		if depth > 0 {
65			diag(ts[start], "unclosed rule")
66			break
67		}
68		if !ok {
69			diag(ts[start], "invalid selector")
70			continue
71		}
72		r := Rule{Selectors: sels}
73		contents := ts[body : i-1]
74		for j := 0; j < len(contents); {
75			for j < len(contents) && (contents[j].Kind == "space" || contents[j].Kind == ";") {
76				j++
77			}
78			if j >= len(contents) {
79				break
80			}
81			b := j
82			// Consume component values: delimiters inside functions or blocks are not
83			// declaration terminators. A stray block is an invalid declaration.
84			depthParen, depthBracket, depthBrace := 0, 0, 0
85			malformedBlock := false
86			for j < len(contents) {
87				switch contents[j].Kind {
88				case "function", "(":
89					depthParen++
90				case ")":
91					if depthParen > 0 {
92						depthParen--
93					}
94				case "[":
95					depthBracket++
96				case "]":
97					if depthBracket > 0 {
98						depthBracket--
99					}
100				case "{":
101					depthBrace++
102					malformedBlock = true
103				case "}":
104					if depthBrace > 0 {
105						depthBrace--
106					}
107				}
108				if contents[j].Kind == ";" && depthParen == 0 && depthBracket == 0 && depthBrace == 0 {
109					break
110				}
111				j++
112			}
113			end := j
114			if malformedBlock {
115				diag(contents[b], "malformed declaration")
116				if j < len(contents) {
117					j++
118				}
119				continue
120			}
121			if j < len(contents) {
122				j++
123			}
124			colon := -1
125			for k := b; k < end; k++ {
126				if contents[k].Kind == ":" {
127					colon = k
128					break
129				}
130			}
131			if colon < 0 {
132				diag(contents[b], "malformed declaration")
133				continue
134			}
135			name := strings.TrimSpace(raw(contents[b:colon]))
136			value := strings.TrimSpace(raw(contents[colon+1 : end]))
137			if name == "" || value == "" && !strings.HasPrefix(name, "--") {
138				diag(contents[b], "malformed declaration")
139				continue
140			}
141			important := false
142			v := Tokenize(value)
143			e := len(v) - 1
144			for e >= 0 && v[e].Kind == "space" {
145				e--
146			}
147			if e >= 0 && strings.EqualFold(v[e].Value, "important") {
148				k := e - 1
149				for k >= 0 && v[k].Kind == "space" {
150					k--
151				}
152				if k >= 0 && v[k].Kind == "!" {
153					important = true
154					value = strings.TrimSpace(raw(v[:k]))
155				}
156			}
157			if !strings.HasPrefix(name, "--") {
158				name = strings.ToLower(name)
159			}
160			d := Declaration{name, value, important, contents[b].Line, contents[b].Col}
161			if len(compileDecls([]Declaration{d})) == 0 {
162				msg := "invalid value for " + name
163				if !strings.HasPrefix(name, "--") && len(shorthandTargets(name)) == 0 {
164					if _, known := propertyIndex(name); !known {
165						msg = "unknown property: " + name
166					}
167				}
168				diag(contents[b], msg)
169			}
170			r.Declarations = append(r.Declarations, d)
171		}
172		sheet.Rules = append(sheet.Rules, r)
173	}
174	return sheet
175}
176func raw(ts []Token) string {
177	var b strings.Builder
178	for _, t := range ts {
179		b.WriteString(t.Raw)
180	}
181	return b.String()
182}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close