@@ -2,10 +2,13 @@ package sourcemapper
2
2
3
3
import (
4
4
"bufio"
5
- "io"
5
+ "bytes"
6
+ "fmt"
7
+ "sort"
6
8
"strconv"
7
9
"strings"
8
10
11
+ "github.com/bcmi-labs/arduino-language-server/handler/textutils"
9
12
"github.com/bcmi-labs/arduino-language-server/lsp"
10
13
)
11
14
@@ -16,9 +19,13 @@ type InoMapper struct {
16
19
CppFile lsp.DocumentURI
17
20
toCpp map [InoLine ]int // Converts File.ino:line -> line
18
21
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
19
23
cppPreprocessed map [int ]InoLine // map of the lines added by the preprocessor: preprocessed line -> File.ino:line
20
24
}
21
25
26
+ // NotIno are lines that do not belongs to an .ino file
27
+ var NotIno = InoLine {"not-ino" , 0 }
28
+
22
29
type SourceRevision struct {
23
30
Version int
24
31
Text string
@@ -41,13 +48,31 @@ func (s *InoMapper) InoToCppLineOk(sourceURI lsp.DocumentURI, line int) (int, bo
41
48
return res , ok
42
49
}
43
50
51
+ // InoToCppLSPRange convert a lsp.Ranger reference to a .ino into a lsp.Range to .cpp
44
52
func (s * InoMapper ) InoToCppLSPRange (sourceURI lsp.DocumentURI , r lsp.Range ) lsp.Range {
45
53
res := r
46
54
res .Start .Line = s .InoToCppLine (sourceURI , r .Start .Line )
47
55
res .End .Line = s .InoToCppLine (sourceURI , r .End .Line )
48
56
return res
49
57
}
50
58
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
+
51
76
// CppToInoLine converts a target (.cpp) line into a source.ino:line
52
77
func (s * InoMapper ) CppToInoLine (targetLine int ) (string , int ) {
53
78
res := s .toIno [targetLine ]
@@ -75,17 +100,22 @@ func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) {
75
100
}
76
101
77
102
// CreateInoMapper create a InoMapper from the given target file
78
- func CreateInoMapper (targetFile io. Reader ) * InoMapper {
103
+ func CreateInoMapper (targetFile [] byte ) * InoMapper {
79
104
mapper := & InoMapper {
80
105
toCpp : map [InoLine ]int {},
81
106
toIno : map [int ]InoLine {},
107
+ inoPreprocessed : map [InoLine ]int {},
82
108
cppPreprocessed : map [int ]InoLine {},
109
+ CppText : & SourceRevision {
110
+ Version : 1 ,
111
+ Text : string (targetFile ),
112
+ },
83
113
}
84
114
85
115
sourceFile := ""
86
116
sourceLine := - 1
87
117
targetLine := 0
88
- scanner := bufio .NewScanner (targetFile )
118
+ scanner := bufio .NewScanner (bytes . NewReader ( targetFile ) )
89
119
for scanner .Scan () {
90
120
lineStr := scanner .Text ()
91
121
if strings .HasPrefix (lineStr , "#line" ) {
@@ -95,9 +125,12 @@ func CreateInoMapper(targetFile io.Reader) *InoMapper {
95
125
sourceLine = l - 1
96
126
}
97
127
sourceFile = unquoteCppString (tokens [2 ])
128
+ mapper .toIno [targetLine ] = NotIno
98
129
} else if sourceFile != "" {
99
130
mapper .mapLine (sourceFile , sourceLine , targetLine )
100
131
sourceLine ++
132
+ } else {
133
+ mapper .toIno [targetLine ] = NotIno
101
134
}
102
135
targetLine ++
103
136
}
@@ -109,6 +142,7 @@ func (s *InoMapper) mapLine(sourceFile string, sourceLine, targetLine int) {
109
142
inoLine := InoLine {sourceFile , sourceLine }
110
143
if line , ok := s .toCpp [inoLine ]; ok {
111
144
s .cppPreprocessed [line ] = inoLine
145
+ s .inoPreprocessed [inoLine ] = line
112
146
}
113
147
s .toCpp [inoLine ] = targetLine
114
148
s .toIno [targetLine ] = inoLine
@@ -123,76 +157,156 @@ func unquoteCppString(str string) string {
123
157
return str
124
158
}
125
159
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 )
196
224
}
197
225
}
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
+ }
198
312
}
0 commit comments