@@ -155,15 +155,15 @@ defmodule Code.Fragment do
155155
156156 def cursor_context ( binary , opts ) when is_binary ( binary ) and is_list ( opts ) do
157157 # CRLF not relevant here - we discard everything before last `\n`
158- binary =
159- case :binary . matches ( binary , "\n " ) do
160- [ ] ->
161- binary
162-
163- matches ->
164- { position , _ } = List . last ( matches )
165- binary_part ( binary , position + 1 , byte_size ( binary ) - position - 1 )
166- end
158+ binary = last_line ( binary )
159+ # case :binary.matches(binary, "\n") do
160+ # [] ->
161+ # binary
162+
163+ # matches ->
164+ # {position, _} = List.last(matches)
165+ # binary_part(binary, position + 1, byte_size(binary) - position - 1)
166+ # end
167167
168168 binary
169169 |> String . to_charlist ( )
@@ -174,13 +174,14 @@ defmodule Code.Fragment do
174174
175175 def cursor_context ( charlist , opts ) when is_list ( charlist ) and is_list ( opts ) do
176176 # CRLF not relevant here - we discard everything before last `\n`
177- charlist =
178- case charlist |> Enum . chunk_by ( & ( & 1 == ?\n ) ) |> List . last ( [ ] ) do
179- [ ?\n | _ ] -> [ ]
180- rest -> rest
181- end
177+ # charlist =
178+ # case charlist |> Enum.chunk_by(&(&1 == ?\n)) |> List.last([]) do
179+ # [?\n | _] -> []
180+ # rest -> rest
181+ # end
182182
183183 charlist
184+ |> last_line
184185 |> :lists . reverse ( )
185186 |> codepoint_cursor_context ( opts )
186187 |> elem ( 0 )
@@ -593,21 +594,19 @@ defmodule Code.Fragment do
593594 | { :dot , inside_dot , charlist }
594595 def surround_context ( fragment , position , options \\ [ ] )
595596
596- def surround_context ( binary , { line , column } , opts ) when is_binary ( binary ) do
597+ def surround_context ( string , { line , column } , opts ) when is_binary ( string ) or is_list ( string ) do
598+ { binary , { lines_before_reversed_lengths , cursor_line_length , lines_after_lengths } } =
599+ join_lines ( string , line , column )
600+
601+ prepended_columns = Enum . sum ( lines_before_reversed_lengths )
602+
597603 binary
598- |> String . split ( [ "\r \n " , "\n " ] )
599- |> Enum . at ( line - 1 , '' )
600604 |> String . to_charlist ( )
601- |> position_surround_context ( line , column , opts )
602- end
603-
604- def surround_context ( charlist , { line , column } , opts ) when is_list ( charlist ) do
605- charlist
606- |> :string . replace ( '\r \n ' , '\n ' , :all )
607- |> :string . join ( '' )
608- |> :string . split ( '\n ' , :all )
609- |> Enum . at ( line - 1 , '' )
610- |> position_surround_context ( line , column , opts )
605+ |> position_surround_context ( line , column + prepended_columns , opts )
606+ |> to_multiline_range (
607+ prepended_columns ,
608+ { lines_before_reversed_lengths , cursor_line_length , lines_after_lengths }
609+ )
611610 end
612611
613612 def surround_context ( other , { _ , _ } = position , opts ) do
@@ -811,6 +810,205 @@ defmodule Code.Fragment do
811810 defp enum_reverse_at ( [ h | t ] , n , acc ) when n > 0 , do: enum_reverse_at ( t , n - 1 , [ h | acc ] )
812811 defp enum_reverse_at ( rest , _ , acc ) , do: { acc , rest }
813812
813+ @ comment_strip ~r/ (?<!\< )\# (?!\{ ).*$/
814+
815+ defp last_line ( binary ) when is_binary ( binary ) do
816+ [ last_line | lines_reverse ] =
817+ binary
818+ |> String . split ( [ "\r \n " , "\n " ] )
819+ |> Enum . reverse ( )
820+
821+ lines_reverse =
822+ lines_reverse
823+ |> Enum . map ( & Regex . replace ( @ comment_strip , & 1 , "" ) )
824+
825+ last_line = last_line
826+
827+ prepend_lines_before ( last_line , lines_reverse )
828+ |> IO . chardata_to_string ( )
829+ end
830+
831+ defp last_line ( charlist ) when is_list ( charlist ) do
832+ [ last_line | lines_reverse ] =
833+ charlist
834+ |> :string . replace ( '\r \n ' , '\n ' , :all )
835+ |> :string . join ( '' )
836+ |> :string . split ( '\n ' , :all )
837+ |> Enum . reverse ( )
838+
839+ lines_reverse =
840+ lines_reverse
841+ |> Enum . map ( & Regex . replace ( @ comment_strip , List . to_string ( & 1 ) , "" ) )
842+
843+ last_line = last_line |> List . to_string ( )
844+
845+ prepend_lines_before ( last_line , lines_reverse )
846+ |> IO . chardata_to_string ( )
847+ |> String . to_charlist ( )
848+ end
849+
850+ defp prepend_lines_before ( cursor_line , lines_before_reverse ) do
851+ lines_before_reverse
852+ |> Enum . reduce_while ( [ cursor_line ] , fn line , [ head | _ ] = acc ->
853+ line_trimmed = String . trim ( line )
854+ acc_trimmed = String . trim_leading ( head )
855+
856+ if String . starts_with? ( acc_trimmed , "." ) or line_trimmed == "" or
857+ String . ends_with? ( line_trimmed , "." ) or String . ends_with? ( line_trimmed , "(" ) do
858+ { :cont , [ line | acc ] }
859+ else
860+ { :halt , acc }
861+ end
862+ end )
863+ end
864+
865+ defp append_lines_after ( cursor_line , lines_after ) do
866+ lines_after
867+ |> Enum . reduce_while ( [ cursor_line ] , fn line , [ head | _ ] = acc ->
868+ line_trimmed = String . trim_leading ( line )
869+ acc_trimmed = String . trim ( head )
870+
871+ if String . starts_with? ( line_trimmed , "." ) or acc_trimmed == "" or
872+ String . ends_with? ( acc_trimmed , "." ) or String . ends_with? ( acc_trimmed , "(" ) do
873+ { :cont , [ line | acc ] }
874+ else
875+ { :halt , acc }
876+ end
877+ end )
878+ |> Enum . reverse ( )
879+ end
880+
881+ def join_lines ( binary , line , column ) when is_binary ( binary ) do
882+ lines =
883+ binary
884+ |> String . split ( [ "\r \n " , "\n " ] )
885+
886+ lines_before_reverse =
887+ if line > 1 do
888+ lines
889+ |> Enum . slice ( 0 .. ( line - 2 ) )
890+ |> Enum . reverse ( )
891+ |> Enum . map ( & Regex . replace ( @ comment_strip , & 1 , "" ) )
892+ else
893+ [ ]
894+ end
895+
896+ lines_after =
897+ lines
898+ |> Enum . slice ( line .. - 1 )
899+ |> Enum . map ( & Regex . replace ( @ comment_strip , & 1 , "" ) )
900+
901+ cursor_line =
902+ lines
903+ |> Enum . at ( line - 1 )
904+
905+ cursor_line_stripped = Regex . replace ( @ comment_strip , cursor_line , "" )
906+
907+ cursor_line_stripped =
908+ if column - 1 > String . length ( cursor_line_stripped ) do
909+ cursor_line
910+ else
911+ cursor_line_stripped
912+ end
913+
914+ added_before_lines = prepend_lines_before ( cursor_line_stripped , lines_before_reverse )
915+ [ _ | added_after_lines ] = append_lines_after ( cursor_line_stripped , lines_after )
916+
917+ built =
918+ [ added_before_lines , added_after_lines ]
919+ |> IO . chardata_to_string ( )
920+
921+ { built ,
922+ { Enum . map ( lines_before_reverse , & String . length / 1 ) , String . length ( cursor_line_stripped ) ,
923+ Enum . map ( lines_after , & String . length / 1 ) } }
924+ end
925+
926+ def join_lines ( charlist , line , column ) when is_list ( charlist ) do
927+ lines =
928+ charlist
929+ |> :string . replace ( '\r \n ' , '\n ' , :all )
930+ |> :string . join ( '' )
931+ |> :string . split ( '\n ' , :all )
932+
933+ lines_before_reverse =
934+ if line > 1 do
935+ lines
936+ |> Enum . slice ( 0 .. ( line - 2 ) )
937+ |> Enum . reverse ( )
938+ |> Enum . map ( & Regex . replace ( @ comment_strip , List . to_string ( & 1 ) , "" ) )
939+ else
940+ [ ]
941+ end
942+
943+ lines_after =
944+ lines
945+ |> Enum . slice ( line .. - 1 )
946+ |> Enum . map ( & Regex . replace ( @ comment_strip , List . to_string ( & 1 ) , "" ) )
947+
948+ cursor_line =
949+ lines
950+ |> Enum . at ( line - 1 )
951+ |> List . to_string ( )
952+
953+ cursor_line_stripped = Regex . replace ( @ comment_strip , cursor_line , "" )
954+
955+ cursor_line_stripped =
956+ if column - 1 > String . length ( cursor_line_stripped ) do
957+ # no comment strip if cursor is inside a comment
958+ # we want to provide results inside comment
959+ cursor_line
960+ else
961+ cursor_line_stripped
962+ end
963+
964+ added_before_lines = prepend_lines_before ( cursor_line_stripped , lines_before_reverse )
965+ [ _ | added_after_lines ] = append_lines_after ( cursor_line_stripped , lines_after )
966+
967+ built =
968+ [ added_before_lines , added_after_lines ]
969+ |> IO . chardata_to_string ( )
970+
971+ { built ,
972+ { Enum . map ( lines_before_reverse , & String . length / 1 ) , String . length ( cursor_line_stripped ) ,
973+ Enum . map ( lines_after , & String . length / 1 ) } }
974+ end
975+
976+ defp to_multiline_range ( :none , _ , _ ) , do: :none
977+
978+ defp to_multiline_range (
979+ % { begin: { begin_line , begin_column } , end: { end_line , end_column } } = context ,
980+ prepended ,
981+ { lines_before_reversed_lengths , cursor_line_length , lines_after_lengths }
982+ ) do
983+ { begin_line , begin_column } =
984+ lines_before_reversed_lengths
985+ |> Enum . reduce_while ( { begin_line , begin_column - prepended } , fn line_length ,
986+ { acc_line , acc_column } ->
987+ if acc_column < 1 do
988+ { :cont , { acc_line - 1 , acc_column + line_length } }
989+ else
990+ { :halt , { acc_line , acc_column } }
991+ end
992+ end )
993+
994+ { end_line , end_column } =
995+ [ cursor_line_length | lines_after_lengths ]
996+ |> Enum . reduce_while ( { end_line , end_column - prepended } , fn line_length ,
997+ { acc_line , acc_column } ->
998+ if acc_column > line_length + 1 do
999+ { :cont , { acc_line + 1 , acc_column - line_length } }
1000+ else
1001+ { :halt , { acc_line , acc_column } }
1002+ end
1003+ end )
1004+
1005+ context
1006+ |> Map . merge ( % {
1007+ begin: { begin_line , begin_column } ,
1008+ end: { end_line , end_column }
1009+ } )
1010+ end
1011+
8141012 @ doc """
8151013 Receives a string and returns a quoted expression
8161014 with a cursor at the nearest argument position.
0 commit comments