Skip to content

Commit 5f19408

Browse files
committed
Added methods to apply text-changes in sketch mapper
1 parent 4401a25 commit 5f19408

File tree

3 files changed

+234
-84
lines changed

3 files changed

+234
-84
lines changed

Diff for: handler/handler.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package handler
22

33
import (
4-
"bytes"
54
"context"
65
"fmt"
76
"io"
87
"log"
98
"os"
109
"regexp"
10+
"strconv"
1111
"strings"
1212
"time"
1313

@@ -281,7 +281,7 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp.
281281
handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp")
282282

283283
if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil {
284-
handler.sketchMapper = sourcemapper.CreateInoMapper(bytes.NewReader(cppContent))
284+
handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent)
285285
} else {
286286
return errors.WithMessage(err, "reading generated cpp file from sketch")
287287
}
@@ -399,14 +399,14 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c
399399
} else {
400400
// Fallback: try to apply a multi-line update
401401
data.sourceText = newSourceText
402-
data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text)
402+
//data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text)
403403
*rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang)
404404
return nil
405405
}
406406
}
407407

408408
data.sourceText = newSourceText
409-
data.sourceMap = sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes))
409+
data.sourceMap = sourcemapper.CreateInoMapper(targetBytes)
410410

411411
change.Text = string(targetBytes)
412412
change.Range = nil
@@ -417,7 +417,7 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c
417417
if err != nil {
418418
return err
419419
}
420-
data.sourceMap.Update(0, rang.Start.Line, change.Text)
420+
//data.sourceMap.Update(0, rang.Start.Line, change.Text)
421421

422422
*rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang)
423423
}

Diff for: handler/sourcemapper/ino.go

+187-73
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package sourcemapper
22

33
import (
44
"bufio"
5-
"io"
5+
"bytes"
6+
"fmt"
7+
"sort"
68
"strconv"
79
"strings"
810

11+
"github.com/bcmi-labs/arduino-language-server/handler/textutils"
912
"github.com/bcmi-labs/arduino-language-server/lsp"
1013
)
1114

@@ -16,9 +19,13 @@ type InoMapper struct {
1619
CppFile lsp.DocumentURI
1720
toCpp map[InoLine]int // Converts File.ino:line -> line
1821
toIno map[int]InoLine // Convers line -> File.ino:line
22+
inoPreprocessed map[InoLine]int // map of the lines taken by the preprocessor: File.ino:line -> preprocessed line
1923
cppPreprocessed map[int]InoLine // map of the lines added by the preprocessor: preprocessed line -> File.ino:line
2024
}
2125

26+
// NotIno are lines that do not belongs to an .ino file
27+
var NotIno = InoLine{"not-ino", 0}
28+
2229
type SourceRevision struct {
2330
Version int
2431
Text string
@@ -41,13 +48,31 @@ func (s *InoMapper) InoToCppLineOk(sourceURI lsp.DocumentURI, line int) (int, bo
4148
return res, ok
4249
}
4350

51+
// InoToCppLSPRange convert a lsp.Ranger reference to a .ino into a lsp.Range to .cpp
4452
func (s *InoMapper) InoToCppLSPRange(sourceURI lsp.DocumentURI, r lsp.Range) lsp.Range {
4553
res := r
4654
res.Start.Line = s.InoToCppLine(sourceURI, r.Start.Line)
4755
res.End.Line = s.InoToCppLine(sourceURI, r.End.Line)
4856
return res
4957
}
5058

59+
// InoToCppLSPRangeOk convert a lsp.Ranger reference to a .ino into a lsp.Range to .cpp and returns
60+
// true if the conversion is successful or false if the conversion is invalid.
61+
func (s *InoMapper) InoToCppLSPRangeOk(sourceURI lsp.DocumentURI, r lsp.Range) (lsp.Range, bool) {
62+
res := r
63+
if l, ok := s.InoToCppLineOk(sourceURI, r.Start.Line); ok {
64+
res.Start.Line = l
65+
} else {
66+
return res, false
67+
}
68+
if l, ok := s.InoToCppLineOk(sourceURI, r.End.Line); ok {
69+
res.End.Line = l
70+
} else {
71+
return res, false
72+
}
73+
return res, true
74+
}
75+
5176
// CppToInoLine converts a target (.cpp) line into a source.ino:line
5277
func (s *InoMapper) CppToInoLine(targetLine int) (string, int) {
5378
res := s.toIno[targetLine]
@@ -75,17 +100,22 @@ func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) {
75100
}
76101

77102
// CreateInoMapper create a InoMapper from the given target file
78-
func CreateInoMapper(targetFile io.Reader) *InoMapper {
103+
func CreateInoMapper(targetFile []byte) *InoMapper {
79104
mapper := &InoMapper{
80105
toCpp: map[InoLine]int{},
81106
toIno: map[int]InoLine{},
107+
inoPreprocessed: map[InoLine]int{},
82108
cppPreprocessed: map[int]InoLine{},
109+
CppText: &SourceRevision{
110+
Version: 1,
111+
Text: string(targetFile),
112+
},
83113
}
84114

85115
sourceFile := ""
86116
sourceLine := -1
87117
targetLine := 0
88-
scanner := bufio.NewScanner(targetFile)
118+
scanner := bufio.NewScanner(bytes.NewReader(targetFile))
89119
for scanner.Scan() {
90120
lineStr := scanner.Text()
91121
if strings.HasPrefix(lineStr, "#line") {
@@ -95,9 +125,12 @@ func CreateInoMapper(targetFile io.Reader) *InoMapper {
95125
sourceLine = l - 1
96126
}
97127
sourceFile = unquoteCppString(tokens[2])
128+
mapper.toIno[targetLine] = NotIno
98129
} else if sourceFile != "" {
99130
mapper.mapLine(sourceFile, sourceLine, targetLine)
100131
sourceLine++
132+
} else {
133+
mapper.toIno[targetLine] = NotIno
101134
}
102135
targetLine++
103136
}
@@ -109,6 +142,7 @@ func (s *InoMapper) mapLine(sourceFile string, sourceLine, targetLine int) {
109142
inoLine := InoLine{sourceFile, sourceLine}
110143
if line, ok := s.toCpp[inoLine]; ok {
111144
s.cppPreprocessed[line] = inoLine
145+
s.inoPreprocessed[inoLine] = line
112146
}
113147
s.toCpp[inoLine] = targetLine
114148
s.toIno[targetLine] = inoLine
@@ -123,76 +157,156 @@ func unquoteCppString(str string) string {
123157
return str
124158
}
125159

126-
// Update performs an update to the SourceMap considering the deleted lines, the
127-
// insertion line and the inserted text
128-
func (s *InoMapper) Update(deletedLines, insertLine int, insertText string) {
129-
// for i := 1; i <= deletedLines; i++ {
130-
// sourceLine := insertLine + 1
131-
// targetLine := s.toCpp[sourceLine]
132-
133-
// // Shift up all following lines by one and put them into a new map
134-
// newMappings := make(map[int]int)
135-
// maxSourceLine, maxTargetLine := 0, 0
136-
// for t, s := range s.toIno {
137-
// if t > targetLine && s > sourceLine {
138-
// newMappings[t-1] = s - 1
139-
// } else if s > sourceLine {
140-
// newMappings[t] = s - 1
141-
// } else if t > targetLine {
142-
// newMappings[t-1] = s
143-
// }
144-
// if s > maxSourceLine {
145-
// maxSourceLine = s
146-
// }
147-
// if t > maxTargetLine {
148-
// maxTargetLine = t
149-
// }
150-
// }
151-
152-
// // Remove mappings for the deleted line
153-
// delete(s.toIno, maxTargetLine)
154-
// delete(s.toCpp, maxSourceLine)
155-
156-
// // Copy the mappings from the intermediate map
157-
// copyMappings(s.toIno, s.toCpp, newMappings)
158-
// }
159-
160-
// addedLines := strings.Count(insertText, "\n")
161-
// if addedLines > 0 {
162-
// targetLine := s.toCpp[insertLine]
163-
164-
// // Shift down all following lines and put them into a new map
165-
// newMappings := make(map[int]int)
166-
// for t, s := range s.toIno {
167-
// if t > targetLine && s > insertLine {
168-
// newMappings[t+addedLines] = s + addedLines
169-
// } else if s > insertLine {
170-
// newMappings[t] = s + addedLines
171-
// } else if t > targetLine {
172-
// newMappings[t+addedLines] = s
173-
// }
174-
// }
175-
176-
// // Add mappings for the added lines
177-
// for i := 1; i <= addedLines; i++ {
178-
// s.toIno[targetLine+i] = insertLine + i
179-
// s.toCpp[insertLine+i] = targetLine + i
180-
// }
181-
182-
// // Copy the mappings from the intermediate map
183-
// copyMappings(s.toIno, s.toCpp, newMappings)
184-
// }
185-
}
186-
187-
func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) {
188-
for t, s := range newMappings {
189-
sourceLineMap[t] = s
190-
targetLineMap[s] = t
191-
}
192-
for t, s := range newMappings {
193-
// In case multiple target lines are present for a source line, use the last one
194-
if t > targetLineMap[s] {
195-
targetLineMap[s] = t
160+
// ApplyTextChange performs the text change and updates both .ino and .cpp files.
161+
// It returns true if the change is "dirty", this happens when the change alters preprocessed lines
162+
// and a new preprocessing may be probably required.
163+
func (s *InoMapper) ApplyTextChange(inoURI lsp.DocumentURI, inoChange lsp.TextDocumentContentChangeEvent) (dirty bool) {
164+
inoRange := *inoChange.Range
165+
cppRange := s.InoToCppLSPRange(inoURI, inoRange)
166+
deletedLines := inoRange.End.Line - inoRange.Start.Line
167+
168+
// Apply text changes
169+
newText, err := textutils.ApplyTextChange(s.CppText.Text, cppRange, inoChange.Text)
170+
if err != nil {
171+
panic("error replacing text: " + err.Error())
172+
}
173+
s.CppText.Text = newText
174+
s.CppText.Version++
175+
176+
// Update line references
177+
for deletedLines > 0 {
178+
dirty = dirty || s.deleteCppLine(cppRange.Start.Line)
179+
deletedLines--
180+
}
181+
addedLines := strings.Count(inoChange.Text, "\n") - 1
182+
for addedLines > 0 {
183+
dirty = dirty || s.addInoLine(cppRange.Start.Line)
184+
}
185+
if _, is := s.cppPreprocessed[cppRange.Start.Line]; is {
186+
dirty = true
187+
}
188+
return
189+
}
190+
191+
func (s *InoMapper) addInoLine(cppLine int) (dirty bool) {
192+
preprocessToShiftCpp := map[InoLine]bool{}
193+
194+
addedInoLine := s.toIno[cppLine]
195+
carry := s.toIno[cppLine]
196+
carry.Line++
197+
for {
198+
next, ok := s.toIno[cppLine+1]
199+
s.toIno[cppLine+1] = carry
200+
s.toCpp[carry] = cppLine + 1
201+
if !ok {
202+
break
203+
}
204+
205+
if next.File == addedInoLine.File && next.Line >= addedInoLine.Line {
206+
if _, is := s.inoPreprocessed[next]; is {
207+
// fmt.Println("Adding", next, "to cpp to shift")
208+
preprocessToShiftCpp[next] = true
209+
}
210+
next.Line++
211+
}
212+
213+
carry = next
214+
cppLine++
215+
}
216+
217+
// dumpCppToInoMap(s.toIno)
218+
219+
preprocessToShiftIno := []InoLine{}
220+
for inoPre := range s.inoPreprocessed {
221+
// fmt.Println(">", inoPre, addedInoLine)
222+
if inoPre.File == addedInoLine.File && inoPre.Line >= addedInoLine.Line {
223+
preprocessToShiftIno = append(preprocessToShiftIno, inoPre)
196224
}
197225
}
226+
for inoPre := range preprocessToShiftCpp {
227+
l := s.inoPreprocessed[inoPre]
228+
delete(s.cppPreprocessed, l)
229+
s.inoPreprocessed[inoPre] = l + 1
230+
s.cppPreprocessed[l+1] = inoPre
231+
}
232+
for _, inoPre := range preprocessToShiftIno {
233+
l := s.inoPreprocessed[inoPre]
234+
delete(s.inoPreprocessed, inoPre)
235+
inoPre.Line++
236+
s.inoPreprocessed[inoPre] = l
237+
s.cppPreprocessed[l] = inoPre
238+
s.toIno[l] = inoPre
239+
}
240+
241+
return
242+
}
243+
244+
func (s *InoMapper) deleteCppLine(line int) (dirty bool) {
245+
removed := s.toIno[line]
246+
for i := line + 1; ; i++ {
247+
shifted, ok := s.toIno[i]
248+
if !ok {
249+
delete(s.toIno, i-1)
250+
break
251+
}
252+
s.toIno[i-1] = shifted
253+
if shifted != NotIno {
254+
s.toCpp[shifted] = i - 1
255+
}
256+
}
257+
258+
if _, ok := s.inoPreprocessed[removed]; ok {
259+
dirty = true
260+
}
261+
262+
for curr := removed; ; curr.Line++ {
263+
next := curr
264+
next.Line++
265+
266+
shifted, ok := s.toCpp[next]
267+
if !ok {
268+
delete(s.toCpp, curr)
269+
break
270+
}
271+
s.toCpp[curr] = shifted
272+
s.toIno[shifted] = curr
273+
274+
if l, ok := s.inoPreprocessed[next]; ok {
275+
s.inoPreprocessed[curr] = l
276+
s.cppPreprocessed[l] = curr
277+
delete(s.inoPreprocessed, next)
278+
279+
s.toIno[l] = curr
280+
}
281+
}
282+
return
283+
}
284+
285+
func dumpCppToInoMap(s map[int]InoLine) {
286+
last := 0
287+
for cppLine := range s {
288+
if last < cppLine {
289+
last = cppLine
290+
}
291+
}
292+
for line := 0; line <= last; line++ {
293+
target := s[line]
294+
fmt.Printf("%5d -> %s:%d\n", line, target.File, target.Line)
295+
}
296+
}
297+
298+
func dumpInoToCppMap(s map[InoLine]int) {
299+
keys := []InoLine{}
300+
for k := range s {
301+
keys = append(keys, k)
302+
}
303+
sort.Slice(keys, func(i, j int) bool {
304+
return keys[i].File < keys[j].File ||
305+
(keys[i].File == keys[j].File && keys[i].Line < keys[j].Line)
306+
})
307+
for _, k := range keys {
308+
inoLine := k
309+
cppLine := s[inoLine]
310+
fmt.Printf("%s:%d -> %d\n", inoLine.File, inoLine.Line, cppLine)
311+
}
198312
}

0 commit comments

Comments
 (0)