@@ -2,10 +2,13 @@ package sourcemapper
22
33import (
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+
2229type 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
4452func (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
5277func (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