Skip to content

Commit d96e478

Browse files
adding some tests of the editor
1 parent 764135b commit d96e478

File tree

4 files changed

+1395
-473
lines changed

4 files changed

+1395
-473
lines changed

playground/internal/editor/codeBox_test.go

Lines changed: 0 additions & 121 deletions
This file was deleted.

playground/internal/editor/editor.go

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package editor
22

3-
import "strings"
3+
import (
4+
"strings"
5+
"unicode"
6+
)
47

58
type CodeBoxWrapper interface {
69
Code() string
@@ -11,15 +14,15 @@ type CodeBoxWrapper interface {
1114
SetSelection(start, end int, code string)
1215
}
1316

14-
type codeEditor struct {
15-
wrapper CodeBoxWrapper
16-
}
17-
1817
func ProcessKeyDown(wrapper CodeBoxWrapper, key string, shift, ctrl bool) bool {
19-
ce := &codeEditor{wrapper: wrapper}
18+
ce := &codeEditor{CodeBoxWrapper: wrapper}
2019
return ce.handleKeyDown(key, shift, ctrl)
2120
}
2221

22+
type codeEditor struct {
23+
CodeBoxWrapper
24+
}
25+
2326
func (ce *codeEditor) handleKeyDown(key string, shift, ctrl bool) bool {
2427
switch key {
2528
case `Tab`:
@@ -45,8 +48,8 @@ func (ce *codeEditor) handleKeyDown(key string, shift, ctrl bool) bool {
4548
case `[`:
4649
return ce.insertPair(ctrl, `[`, `]`)
4750
case `Escape`:
48-
ce.wrapper.EmitEscape()
49-
return false
51+
ce.EmitEscape()
52+
return true
5053
default:
5154
return false
5255
}
@@ -60,7 +63,7 @@ func (ce *codeEditor) handleTab(shift, ctrl bool) bool {
6063
return false
6164
}
6265

63-
start, end := ce.wrapper.GetSelection()
66+
start, end := ce.GetSelection()
6467
if !shift && start == end {
6568
// Insert tab character at caret.
6669
ce.insertAtSelection("\t", ``, false)
@@ -86,13 +89,13 @@ func (ce *codeEditor) handleMultilineComment(shift, ctrl bool) bool {
8689
return false
8790
}
8891

89-
start, end := ce.wrapper.GetSelection()
92+
start, end := ce.GetSelection()
9093
if start != end {
9194
// If a selection, just prerform default behavior.
9295
return false
9396
}
9497

95-
if start <= 0 || ce.wrapper.Code()[start-1] != '/' {
98+
if start <= 0 || ce.Code()[start-1] != '/' {
9699
// Not preceded by '/', allow default behavior.
97100
return false
98101
}
@@ -109,8 +112,8 @@ func (ce *codeEditor) handleNewline(shift, ctrl bool) bool {
109112
return false
110113
}
111114

112-
code := ce.wrapper.Code()
113-
start, end := ce.wrapper.GetSelection()
115+
code := ce.Code()
116+
start, end := ce.GetSelection()
114117
before := "\n" + ce.indentAt(start)
115118
after := ``
116119

@@ -142,7 +145,7 @@ func (ce *codeEditor) handleSave(shift, ctrl bool) bool {
142145
return false
143146
}
144147

145-
ce.wrapper.EmitSave()
148+
ce.EmitSave()
146149
return true
147150
}
148151

@@ -153,57 +156,117 @@ func (ce *codeEditor) handleCommentToggle(shift, ctrl bool) bool {
153156
return false
154157
}
155158

159+
const commentPrefix = `// `
160+
156161
startLine, endLine := ce.getSelectedLines()
157162
leastIndent := -1
163+
nonBlankLineCount := 0
158164
ce.foreachLine(startLine, endLine, func(line string, start, end int) bool {
159-
if trimmed := strings.TrimSpace(line); trimmed != `` {
165+
if trimmed := trimLeftSpace(line); trimmed != `` {
160166
indent := len(line) - len(trimmed)
161167
if leastIndent < 0 || indent < leastIndent {
162168
leastIndent = indent
163169
}
170+
nonBlankLineCount++
164171
}
165172
return true
166173
})
167174

175+
if nonBlankLineCount <= 0 {
176+
// No non-blank lines, nothing to comment/uncomment.
177+
return true
178+
}
179+
168180
containsUncommentedLine := false
169181
ce.foreachLine(startLine, endLine, func(line string, start, end int) bool {
170182
trimmed := strings.TrimSpace(line)
171-
if trimmed != `` && !strings.HasPrefix(trimmed, `//`) {
183+
if trimmed != `` && !strings.HasPrefix(trimmed, commentPrefix) {
172184
containsUncommentedLine = true
173185
}
174186
return true
175187
})
176188

177189
if containsUncommentedLine {
178-
// Comment the selected lines.
179-
// TODO(grantnelson-wf): Implement
180-
println("TODO: Handle commenting selected lines")
190+
// Comment the selected lines including any commented lines.
191+
code := ce.Code()[:startLine]
192+
ce.foreachLine(startLine, endLine, func(line string, start, end int) bool {
193+
if line == "\n" || strings.TrimSpace(line) == `` {
194+
code += line // Empty line, just add as-is.
195+
return true
196+
}
197+
code += line[:leastIndent] + commentPrefix + line[leastIndent:]
198+
return true
199+
})
200+
code += ce.Code()[endLine:]
201+
202+
start, end := ce.GetSelection()
203+
// TODO(grantnelson-wf): Handle when the end or start ends up before the comment prefix.
204+
newStart := start + len(commentPrefix)
205+
newEnd := end + len(commentPrefix)*nonBlankLineCount
206+
207+
ce.SetCode(code)
208+
ce.SetSelection(newStart, newEnd, code)
209+
return true
181210
}
182211

183-
// TODO(grantnelson-wf): Implement
184-
println("TODO: Handle uncommenting selected lines")
212+
// Uncomment any line that starts with the comment prefix (and preceding whitespace).
213+
code := ce.Code()[:startLine]
214+
ce.foreachLine(startLine, endLine, func(line string, start, end int) bool {
215+
if trimmed := trimLeftSpace(line); trimmed != `` {
216+
if index := strings.Index(line, commentPrefix); index >= 0 {
217+
code += line[:index] + line[index+len(commentPrefix):]
218+
return true
219+
}
220+
}
221+
code += line // Leave line as is
222+
return true
223+
})
224+
code += ce.Code()[endLine:]
225+
226+
start, end := ce.GetSelection()
227+
// TODO(grantnelson-wf): Handle when the end or start ends up before the comment prefix.
228+
newStart := start - len(commentPrefix)
229+
newEnd := end - len(commentPrefix)*nonBlankLineCount
230+
231+
ce.SetCode(code)
232+
ce.SetSelection(newStart, newEnd, code)
185233
return true
186234
}
187235

236+
// getSelectedLines returns the start and end character indices of that
237+
// include the start of the selection and the end of the selection.
238+
// The returned start should be at the beginning of the line after the "\n"
239+
// or at the front of all the code.
240+
// The returned end should be after the "\n" after the selection
241+
// or at the end of all the code.
188242
func (ce *codeEditor) getSelectedLines() (int, int) {
189-
code := ce.wrapper.Code()
190-
start, stop := ce.wrapper.GetSelection()
243+
code := ce.Code()
244+
codeLen := len(code)
245+
start, end := ce.GetSelection()
191246
startLine := 0
192-
if start > 0 && start <= len(code) {
247+
if start >= 0 && start <= codeLen {
193248
startLine = strings.LastIndex(code[:start], "\n") + 1
249+
if startLine < 0 {
250+
startLine = 0
251+
}
194252
}
195-
endLine := len(code)
196-
if stop > 0 && stop <= len(code) {
197-
endLine = strings.Index(code[stop:], "\n")
253+
endLine := codeLen
254+
if end >= 0 && end <= codeLen {
255+
endLine = strings.Index(code[end:], "\n")
256+
if endLine < 0 {
257+
endLine = codeLen
258+
} else {
259+
endLine += end + 1
260+
}
198261
}
199262
return startLine, endLine
200263
}
201264

202265
func (ce *codeEditor) foreachLine(start, end int, yield func(line string, start, end int) bool) bool {
203-
code := ce.wrapper.Code()
266+
code := ce.Code()
204267
for i := start; i < end; {
205-
lineEnd := strings.Index(code[i:end], "\n")
206-
if lineEnd < 0 {
268+
lineEnd := strings.Index(code[i:end], "\n") + 1
269+
if lineEnd <= 0 {
207270
lineEnd = end
208271
} else {
209272
lineEnd += i
@@ -212,7 +275,7 @@ func (ce *codeEditor) foreachLine(start, end int, yield func(line string, start,
212275
if !yield(line, i, lineEnd) {
213276
return false
214277
}
215-
i = lineEnd + 1
278+
i = lineEnd
216279
}
217280
return true
218281
}
@@ -228,8 +291,8 @@ func (ce *codeEditor) insertPair(ctrl bool, before, after string) bool {
228291
}
229292

230293
func (ce *codeEditor) insertAtSelection(before, after string, keepSelection bool) {
231-
code := ce.wrapper.Code()
232-
start, end := ce.wrapper.GetSelection()
294+
code := ce.Code()
295+
start, end := ce.GetSelection()
233296

234297
var head, tail, selected string
235298
if start > 0 {
@@ -246,12 +309,12 @@ func (ce *codeEditor) insertAtSelection(before, after string, keepSelection bool
246309
newStart := start + len(before)
247310
newEnd := newStart + len(selected)
248311

249-
ce.wrapper.SetCode(code)
250-
ce.wrapper.SetSelection(newStart, newEnd, code)
312+
ce.SetCode(code)
313+
ce.SetSelection(newStart, newEnd, code)
251314
}
252315

253316
func (ce *codeEditor) indentAt(start int) string {
254-
code := ce.wrapper.Code()
317+
code := ce.Code()
255318
if start <= 0 || start > len(code) {
256319
return ``
257320
}
@@ -272,7 +335,7 @@ func (ce *codeEditor) indentAt(start int) string {
272335
// Currently this does not account for braces inside strings or comments.
273336
// It returns -1 if no matching opening brace is found.
274337
func (ce *codeEditor) findMatchingOpeningBrace(caret int) int {
275-
code := ce.wrapper.Code()
338+
code := ce.Code()
276339
if caret <= 0 || caret > len(code) {
277340
return -1
278341
}
@@ -305,3 +368,7 @@ func (ce *codeEditor) findMatchingOpeningBrace(caret int) int {
305368
}
306369
return -1
307370
}
371+
372+
func trimLeftSpace(s string) string {
373+
return strings.TrimLeftFunc(s, unicode.IsSpace)
374+
}

0 commit comments

Comments
 (0)