11package editor
22
3- import "strings"
3+ import (
4+ "strings"
5+ "unicode"
6+ )
47
58type 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-
1817func 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+
2326func (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.
188242func (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
202265func (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
230293func (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
253316func (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.
274337func (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