Skip to content

Commit fa4649a

Browse files
committed
Implement range focus with special case for lists.
1 parent 702341d commit fa4649a

2 files changed

Lines changed: 75 additions & 7 deletions

File tree

rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -770,12 +770,7 @@ public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRange
770770
var start = Locations.toRascalPosition(uri, range.getStart(), columns);
771771
var end = Locations.toRascalPosition(uri, range.getEnd(), columns);
772772
// compute the focus list at the end of the range
773-
var focus = TreeSearch.computeFocusList(tree, end.getLine(), end.getCharacter())
774-
.stream()
775-
.map(ITree.class::cast)
776-
// check for containment of the start of the range
777-
.filter(t -> Ranges.containsPosition(Locations.toRange(TreeAdapter.getLocation(t), columns), start))
778-
.collect(VF.listWriter());
773+
var focus = TreeSearch.computeFocusList(tree, start.getLine(), start.getCharacter(), end.getLine(), end.getCharacter());
779774

780775
var opts = getFormattingOptions(params.getOptions());
781776
return contribs.formatting(focus, opts).get();

rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/locations/impl/TreeSearch.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
*/
2727
package org.rascalmpl.vscode.lsp.util.locations.impl;
2828

29+
import org.apache.logging.log4j.LogManager;
30+
import org.apache.logging.log4j.Logger;
2931
import org.rascalmpl.values.IRascalValueFactory;
3032
import org.rascalmpl.values.parsetrees.ITree;
3133
import org.rascalmpl.values.parsetrees.TreeAdapter;
@@ -40,6 +42,9 @@
4042
*/
4143
public 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

Comments
 (0)