-
Notifications
You must be signed in to change notification settings - Fork 90
/
parser.go
195 lines (175 loc) · 5.85 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.
// The parser package defines interfaces responsible for creating
// an Abstract Syntax Tree like structure of a text document.
//
// It should then be possible to query this structure for
// the name and extend of the various code scopes defined within it.
//
// TODO:
// It should be possible to hook in for example libclang,
// go/ast and other "proper" code parsers. Do these interfaces
// make sense for those or should they be changed?
package parser
import (
"bytes"
"sort"
"sync"
"github.com/limetext/backend/render"
"github.com/limetext/text"
"github.com/quarnster/parser"
)
type (
// The Parser interface is responsible for creating
// a parser.Node structure of a given text data.
Parser interface {
Parse() (*parser.Node, error)
}
// The SyntaxHighlighter interface is responsible for
// identifying the extent and name of code scopes given
// a position in the code buffer this specific SyntaxHighlighter
// is responsible for.
//
// It's expected that the syntax highlighter monkey patches its existing
// scope data rather than performing a full reparse when the underlying
// buffer changes.
//
// This is because a full reparse, for which the Parser interface is responsible,
// will be going on in parallel in a separate thread and the "monkey patch"
// will allow some accuracy in the meantime until the Parse operation has finished.
SyntaxHighlighter interface {
// Adjust is called when the underlying text buffer changes at "position"
// with a change of "delta" characters either being inserted or removed.
//
// See note above regarding "monkey patching".
Adjust(position, delta int)
// Returns the Region of the inner most Scope extent which contains "point".
//
// This method can be called a lot by plugins, and should therefore be as
// fast as possible.
ScopeExtent(point int) text.Region
// Returns the full concatenated nested scope name of the scope(s) containing "point".
//
// This method can be called a lot by plugins, and should therefore be as
// fast as possible.
ScopeName(point int) string
// Flatten creates a map where the key is the concatenated nested scope names
// and the key is the render.ViewRegions associated with that key.
//
// This function is only called once by the View, which merges
// the regions into its own region map and adjusts them as appropriate.
Flatten() render.ViewRegionMap
}
nodeHighlighter struct {
rootNode *parser.Node
lastScopeNode *parser.Node
lastScopeBuf bytes.Buffer
lastScopeName string
sync.Mutex
}
)
// Creates a new default implementation of SyntaxHighlighter operating
// on the AST created by "p"'s Parse().
func NewSyntaxHighlighter(p Parser) (SyntaxHighlighter, error) {
if rn, err := p.Parse(); err != nil {
return nil, err
} else {
return &nodeHighlighter{rootNode: rn}, nil
}
}
// Given a text region, returns the innermost node covering that region.
// Side-effects: Writes to nh.lastScopeBuf...
func (nh *nodeHighlighter) findScope(search text.Region, node *parser.Node) *parser.Node {
idx := sort.Search(len(node.Children), func(i int) bool {
return node.Children[i].Range.A >= search.A || node.Children[i].Range.Covers(search)
})
for idx < len(node.Children) {
c := node.Children[idx]
if c.Range.A > search.B {
break
}
if c.Range.Covers(search) {
if node.Name != "" && node != nh.lastScopeNode {
if nh.lastScopeBuf.Len() > 0 {
nh.lastScopeBuf.WriteByte(' ')
}
nh.lastScopeBuf.WriteString(node.Name)
}
return nh.findScope(search, node.Children[idx])
}
idx++
}
if node != nh.lastScopeNode && node.Range.Covers(search) && node.Name != "" {
if nh.lastScopeBuf.Len() > 0 {
nh.lastScopeBuf.WriteByte(' ')
}
nh.lastScopeBuf.WriteString(node.Name)
return node
}
return nil
}
// Caches the full concatenated nested scope name and the innermost node that covers "point".
// TODO: multiple cursors being in different scopes is harsh on the cache...
func (nh *nodeHighlighter) updateScope(point int) {
if nh.rootNode == nil {
return
}
search := text.Region{A: point, B: point + 1}
if nh.lastScopeNode != nil && nh.lastScopeNode.Range.Covers(search) {
if len(nh.lastScopeNode.Children) != 0 {
if no := nh.findScope(search, nh.lastScopeNode); no != nh.lastScopeNode && no != nil {
nh.lastScopeNode = no
nh.lastScopeName = nh.lastScopeBuf.String()
}
}
return
}
nh.lastScopeNode = nil
nh.lastScopeBuf.Reset()
nh.lastScopeNode = nh.findScope(search, nh.rootNode)
nh.lastScopeName = nh.lastScopeBuf.String()
}
func (nh *nodeHighlighter) ScopeExtent(point int) text.Region {
nh.updateScope(point)
if nh.lastScopeNode != nil {
r := nh.lastScopeNode.Range
return text.Region{A: r.A, B: r.B}
}
return text.Region{}
}
func (nh *nodeHighlighter) ScopeName(point int) string {
nh.updateScope(point)
return nh.lastScopeName
}
func (nh *nodeHighlighter) flatten(vrmap render.ViewRegionMap, scopename string, node *parser.Node) {
scopename += " " + node.Name
cur := node.Range
for _, c := range node.Children {
if cur.A <= c.Range.A {
reg := vrmap[scopename]
reg.Flags |= render.DRAW_TEXT
reg.Scope = scopename
reg.Regions.Add(text.Region{A: cur.A, B: c.Range.A})
vrmap[scopename] = reg
}
cur.A = c.Range.B
nh.flatten(vrmap, scopename, c)
}
// Just add the last region if it's not zero sized
if cur.A != cur.B {
reg := vrmap[scopename]
reg.Flags |= render.DRAW_TEXT
reg.Scope = scopename
reg.Regions.Add(text.Region{A: cur.A, B: cur.B})
vrmap[scopename] = reg
}
}
func (nh *nodeHighlighter) Adjust(position, delta int) {
nh.rootNode.Adjust(position, delta)
}
func (nh *nodeHighlighter) Flatten() (ret render.ViewRegionMap) {
ret = make(render.ViewRegionMap)
nh.flatten(ret, "lime.syntax", nh.rootNode)
return
}