2626 */
2727package org .rascalmpl .vscode .lsp .util .locations .impl ;
2828
29+ import org .apache .logging .log4j .LogManager ;
30+ import org .apache .logging .log4j .Logger ;
2931import org .rascalmpl .values .IRascalValueFactory ;
3032import org .rascalmpl .values .parsetrees .ITree ;
3133import org .rascalmpl .values .parsetrees .TreeAdapter ;
4042 */
4143public class TreeSearch {
4244
45+ private static final Logger logger = LogManager .getLogger (TreeSearch .class );
46+ private static final IRascalValueFactory VF = IRascalValueFactory .getInstance ();
47+
4348 private TreeSearch () {}
4449
4550 /**
@@ -85,6 +90,17 @@ else if (line == loc.getEndLine()) {
8590 return true ;
8691 }
8792
93+ private static boolean rightOf (ISourceLocation loc , int line , int column ) {
94+ if (!loc .hasLineColumn ()) {
95+ return false ;
96+ }
97+
98+ if (line > loc .getEndLine ()) {
99+ return true ;
100+ }
101+ return line == loc .getEndLine () && column > loc .getEndColumn ();
102+ }
103+
88104 /**
89105 * Produces a list of trees that are "in focus" at given line and column offset (UTF-24).
90106 *
@@ -99,7 +115,7 @@ else if (line == loc.getEndLine()) {
99115 * @return list of tree that are around the given line/column position, ordered from child to parent.
100116 */
101117 public static IList computeFocusList (ITree tree , int line , int column ) {
102- var lw = IRascalValueFactory . getInstance () .listWriter ();
118+ var lw = VF .listWriter ();
103119 computeFocusList (lw , tree , line , column );
104120 return lw .done ();
105121 }
@@ -157,4 +173,61 @@ private static boolean computeFocusList(IListWriter focus, ITree tree, int line,
157173 // cycles and characters do not have locations
158174 return false ;
159175 }
176+
177+ public static IList computeFocusList (ITree tree , int startLine , int startColumn , int endLine , int endColumn ) {
178+ // Compute the focus for both the start end end positions.
179+ // These foci give us information about the structure of the selection.
180+ final var startList = computeFocusList (tree , startLine , startColumn );
181+ final var endList = computeFocusList (tree , endLine , endColumn );
182+
183+ final var commonSuffix = startList .intersect (endList );
184+ if (commonSuffix .equals (startList ) || commonSuffix .equals (endList )) {
185+ // We do not have enough information to extend the focus
186+ return commonSuffix ;
187+ }
188+ // The range spans multiple subtrees. The easy way out is not to focus farther down than
189+ // their smallest common subtree (i.e. `commonSuffix`) - let's see if we can do any better.
190+ if (TreeAdapter .isList ((ITree ) commonSuffix .get (0 ))) {
191+ return computeListRangeFocus (commonSuffix , startLine , startColumn , endLine , endColumn );
192+ }
193+
194+ return commonSuffix ;
195+ }
196+
197+ private static IList computeListRangeFocus (final IList commonSuffix , int startLine , int startColumn , int endLine , int endColumn ) {
198+ final var parent = (ITree ) commonSuffix .get (0 );
199+ logger .trace ("Computing focus list for {} at range [{}:{}, {}:{}]" , TreeAdapter .getType (parent ), startLine , startColumn , endLine , endColumn );
200+ final var elements = TreeAdapter .getListASTArgs (parent );
201+ final int nElements = elements .length ();
202+
203+ logger .trace ("Smallest common tree is a {} with {} elements" , TreeAdapter .getType (parent ), nElements );
204+ if (inside (TreeAdapter .getLocation ((ITree ) elements .get (0 )), startLine , startColumn ) &&
205+ inside (TreeAdapter .getLocation ((ITree ) elements .get (nElements - 1 )), endLine , endColumn )) {
206+ // The whole list is selected
207+ return commonSuffix ;
208+ }
209+
210+ // Find the elements in the list that are (partially) selected.
211+ final var selected = elements .stream ()
212+ .map (ITree .class ::cast )
213+ .dropWhile (t -> !inside (TreeAdapter .getLocation (t ), startLine , startColumn ))
214+ .takeWhile (t -> rightOf (TreeAdapter .getLocation (t ), endLine , endColumn ))
215+ .collect (VF .listWriter ());
216+ final int nSelected = selected .length ();
217+
218+ logger .trace ("Range covers {} (of {}) elements in the parent list" , nSelected , nElements );
219+ final var firstSelected = TreeAdapter .getLocation ((ITree ) selected .get (0 ));
220+ final var lastSelected = TreeAdapter .getLocation ((ITree ) selected .get (nSelected - 1 ));
221+
222+ final int totalLength = lastSelected .getOffset () - firstSelected .getOffset () + lastSelected .getLength ();
223+ final var selectionLoc = VF .sourceLocation (firstSelected , firstSelected .getOffset (), totalLength ,
224+ firstSelected .getBeginLine (), lastSelected .getEndLine (), firstSelected .getBeginColumn (), lastSelected .getEndColumn ());
225+ final var artificialParent = TreeAdapter .setLocation (VF .appl (TreeAdapter .getProduction (parent ), selected ), selectionLoc );
226+
227+ // Build new focus list
228+ var lw = VF .listWriter ();
229+ lw .append (artificialParent );
230+ lw .appendAll (commonSuffix );
231+ return lw .done ();
232+ }
160233}
0 commit comments