@@ -159,23 +159,84 @@ defmodule ElixirSense.Core.Source do
159159 { prefix , suffix }
160160 end
161161
162+ @ doc """
163+ Splits the given source `src` into a list of lines using all common newline forms.
164+ """
165+ def split_lines ( src , opts \\ [ ] ) do
166+ String . split ( src , [ "\r \n " , "\r " , "\n " ] , opts )
167+ end
168+
169+ @ doc """
170+ Splits `src` at the given `line` and `col`.
171+
172+ If the specified column is past the end of the line,
173+ the text before the split is padded with spaces.
174+ """
162175 @ spec split_at ( String . t ( ) , pos_integer , pos_integer ) :: { String . t ( ) , String . t ( ) }
163- def split_at ( code , line , col ) do
164- pos = find_position ( code , max ( line , 1 ) , max ( col , 1 ) , { 0 , 1 , 1 } )
165- String . split_at ( code , pos )
176+ def split_at ( src , line , col ) do
177+ # Ensure minimum line and column are 1.
178+ line = max ( line , 1 )
179+ col = max ( col , 1 )
180+ lines = split_lines ( src , trim: false )
181+ { prefix , target , suffix } = extract_line ( lines , line )
182+
183+ target_len = String . length ( target )
184+
185+ { before_target , after_target } =
186+ if col - 1 <= target_len do
187+ {
188+ String . slice ( target , 0 , col - 1 ) ,
189+ String . slice ( target , col - 1 , target_len - ( col - 1 ) )
190+ }
191+ else
192+ pad = String . duplicate ( " " , col - 1 - target_len )
193+ { target <> pad , "" }
194+ end
195+
196+ before_part =
197+ case prefix do
198+ [ ] -> before_target
199+ _ -> Enum . join ( prefix , "\n " ) <> "\n " <> before_target
200+ end
201+
202+ after_part =
203+ if suffix == [ ] do
204+ after_target
205+ else
206+ after_target <> "\n " <> Enum . join ( suffix , "\n " )
207+ end
208+
209+ { before_part , after_part }
210+ end
211+
212+ # If the requested line is beyond the number of lines,
213+ # we use the last line as the target.
214+ defp extract_line ( lines , line ) do
215+ if line > length ( lines ) do
216+ { Enum . slice ( lines , 0 , length ( lines ) - 1 ) , List . last ( lines ) || "" , [ ] }
217+ else
218+ { prefix , [ target | suffix ] } = Enum . split ( lines , line - 1 )
219+ { prefix , target , suffix }
220+ end
166221 end
167222
223+ @ doc """
224+ Splits `src` into segments at multiple positions.
225+
226+ The positions should be given as a list of `{line, col}` tuples.
227+ Splitting is performed from the end of the source backwards,
228+ so the final result is a list of segments.
229+ """
168230 @ spec split_at ( String . t ( ) , list ( { pos_integer , pos_integer } ) ) :: list ( String . t ( ) )
169- def split_at ( code , list ) do
170- do_split_at ( code , Enum . reverse ( list ) , [ ] )
231+ def split_at ( src , positions ) when is_list ( positions ) do
232+ do_split_at ( src , Enum . reverse ( positions ) , [ ] )
171233 end
172234
173- defp do_split_at ( code , [ ] , acc ) , do: [ code | acc ]
235+ defp do_split_at ( src , [ ] , acc ) , do: [ src | acc ]
174236
175- defp do_split_at ( code , [ { line , col } | rest ] , acc ) do
176- pos = find_position ( code , max ( line , 1 ) , max ( col , 1 ) , { 0 , 1 , 1 } )
177- { text_before , text_after } = String . split_at ( code , pos )
178- do_split_at ( text_before , rest , [ text_after | acc ] )
237+ defp do_split_at ( src , [ { line , col } | rest ] , acc ) do
238+ { before , after_cursor } = split_at ( src , line , col )
239+ do_split_at ( before , rest , [ after_cursor | acc ] )
179240 end
180241
181242 @ spec text_before ( String . t ( ) , pos_integer , pos_integer ) :: String . t ( )
@@ -415,33 +476,6 @@ defmodule ElixirSense.Core.Source do
415476 end
416477 end
417478
418- defp find_position ( _text , line , col , { pos , line , col } ) do
419- pos
420- end
421-
422- defp find_position ( text , line , col , { pos , current_line , current_col } ) do
423- case String . next_grapheme ( text ) do
424- { grapheme , rest } ->
425- { new_pos , new_line , new_col } =
426- if grapheme in @ line_break do
427- if current_line == line do
428- # this is the line we're lookin for
429- # but it's shorter than expected
430- { pos , current_line , col }
431- else
432- { pos + 1 , current_line + 1 , 1 }
433- end
434- else
435- { pos + 1 , current_line , current_col + 1 }
436- end
437-
438- find_position ( rest , line , col , { new_pos , new_line , new_col } )
439-
440- nil ->
441- pos
442- end
443- end
444-
445479 # since elixir 1.15 previous lines are kept in blocks
446480 # https://github.com/elixir-lang/elixir/commit/faf81cd92c7d6668d2e8115744cc8d06f9bfecba
447481 # skip as __block__ is not a call we are interested in
@@ -681,6 +715,7 @@ defmodule ElixirSense.Core.Source do
681715 end
682716 end
683717
718+ # TODO remove
684719 def concat_module_parts ( [ { :__MODULE__ , _ , nil } | rest ] , current_module , aliases )
685720 when is_atom ( current_module ) and current_module != nil do
686721 case concat_module_parts ( rest , current_module , aliases ) do
@@ -704,10 +739,6 @@ defmodule ElixirSense.Core.Source do
704739
705740 def concat_module_parts ( [ ] , _ , _ ) , do: :error
706741
707- def split_lines ( src , opts \\ [ ] ) do
708- String . split ( src , [ "\r \n " , "\r " , "\n " ] , opts )
709- end
710-
711742 def bitstring_options ( prefix ) do
712743 tokens = Tokenizer . tokenize ( prefix )
713744
0 commit comments