-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TSServer: Smart select API #29071
Comments
related LSP discussion microsoft/language-server-protocol#613 |
/cc @minestarks |
A I think in VS this would roughly map to an implementation of |
Here's some thoughts on how expansion could work: Where should selection stops be added?Walk the ast from the current position up to the root node, adding a selection to the smart select response when encountering:
Examplesclass Foo {
bar(a, b) {
if (a === b) {
return true;
}
return false;
}
} Starting on
class Foo {
bar(a, b) {
if (a === b) {
return true
}
return false;
}
} Starting on
export interface IService {
_serviceBrand: any;
open(host: number, data: any): Promise<any>;
} Starting on
import { x as y, z } from './z';
import { b } from './';
console.log(1); Starting on
|
We've refined our API on the VS Code side and have decided not to include the interface SelectionRangeRequest extends FileLocationRequest {
command: "selectionRange";
arguments: SelectionRangeRequestArgs;
}
interface SelectionRangeRequestArgs extends FileLocationRequestArgs { }
interface SelectionRangeResponse {
body?: SelectionRange;
}
interface SelectionRange {
textSpan: TextSpan;
parent?: SelectionRange;
} The VS Code api also takes a list of positions in the file and we return a list of selection ranges for each of those positions. This avoids extra requests in some cases. We should consider doing this in the TS api as well, as I don't think there's any downside. This would look like: interface SelectionRangeRequest extends FileLocationRequest {
command: "selectionRange";
arguments: SelectionRangeRequestArgs;
}
interface SelectionRangeRequestArgs extends FileRequestArgs {
locations: Location[];
}
interface SelectionRangeResponse {
body?: SelectionRange[];
}
interface SelectionRange {
textSpan: TextSpan;
parent?: SelectionRange;
} Here's the VS Code api: https://github.com/microsoft/vscode/blob/caafa5b6920b0e4b8ce2a8300a61568372da0f7b/src/vs/vscode.d.ts#L3791 /cc @jrieken |
@mjbvz when you mention that blocks should be selected without braces, do you want a) the selection to be begin and end at non-whitespace characters, or do you want b) all characters including whitespace between the braces to be selected?
|
B I think. That's how we've implemented the feature for VS Code's json and html support at least |
Can I get your input on these? type X = { /*|*/x?: string }; type M = { [K in keyof /*|*/any]: string }; const x = { /*|*/y }; function f(...ar/*|*/gs) {} function f(
/*|*/a,
b,
) {} const s = `one two ${
/*|*/three
} four`; Also, I was looking at an existing smart select test in VS Code, and it looks like there are cases where you expect to get a range with an identical parent range? My approach so far includes deduping identical ranges (e.g., in a whole file containing a single function declaration with no surrounding trivia, the function declaration range is the same as the whole file range) on TypeScript's side. Let me know if you'd rather me keep syntactically distinct but positionally redundant ranges in the list. |
De-duplicating the ranges sounds correct to me. @jrieken can you confirm if smart select providers should de-duplicate or not? Some thoughts on those examples: type X = { /*|*/x?: string };
type M = { [K in keyof /*|*/any]: string };
const x = { /*|*/y };
function f(...ar/*|*/gs) {}
function f(
/*|*/a,
b,
) {}
const s = `one two ${
/*|*/three
} four`;
|
It's kind of optional because the editor will deduplicate ranges anyways - so if it hinders an elegant implementation I won't do it. |
@mjbvz @jrieken thanks! So, in my earlier question, I had swapped my "a" and "b" labels such that the screenshots demonstrated the opposite of the options I had written, but I assumed when you responded "B," you were referring to the picture. (I've since edited the question to be consistent.) If your answer is still "B," I'm trying to figure out the logical rule for: when do we select all the way up to braces vs. leaving trivia unselected? E.g., in the if statement we want to select all the way up to the braces, but in the import clause ( I've been trying to think through these scenarios in terms of the probable desired outcomes a user has when expanding a selection, and how easy it is to achieve those outcomes given a particular selection. When braces are on the same line, it's a bit of a toss-up: When the braces span multiple lines, selecting all the way up to the braces seems to be more versatile: Now, the really painful sequences above can be avoided by simply expanding the selection one more time to include the braces, then typing
These scenarios lead me to suggest one of two possible governing rules:
There are possibly other heuristics that could be considered (like the difference between objects, which have a stop just outside the braces, and classes, which select the braces plus the text By the way, I'm in Redmond (visiting from SF) until Friday afternoon, so if it's easier to discuss in person, feel free to ping me or come by the TypeScript team room. |
Updating thread with results of in-person meeting for posterity: we agreed to go with option 2 above and see how it feels once we have it wired up with VS Code. |
For microsoft/TypeScript#29071 This require upstream TS support. Check in experimental support so that TS team can test the ux of this feature
I've checked in a stubbed out VS Code implementation with microsoft/vscode@f635233 This is based on the second version of the api proposal, the one that takes a |
Cool, it works with my WIP branch! It does look like the ranges provided by tsserver are getting merged with Code's other guesses, e.g. when on |
Yeah, that's VSCode itself, expanding words along separators, like |
Ok @mjbvz @jrieken, do you want to give it a try? I'm getting the feeling that I could find little bits of syntax to nitpick and customize forever, but I'm pretty sure at this stage I've covered every example listed in this issue discussion plus some. If you don't find any glaring issues, I'll open a PR. Thanks! |
Thanks @andrewbranch. I've done some initial testing and ran into one problem. With the cursor at the const z = 1;
interface foo {
bar(): void/**/
} In this case, the first returned range is:
which causes VS Code's selection range conversion to fail. The top returned range must contain the initial position. I'll keep testing and share other feedback |
Looking good overall. A few other notes: const callback/**/ = this.fetchCallback(serverRequest.seq); There's currently a stop where just the assignment expression is selected: I don't think this step is necessary. We could jump right to selecting the entire Doc comments are currently not handled. I think these docs should either be included when the element being documented is or as a separate step. For example: const x = 1;
/**
* docs
*/
function foo/**/() {} Would have some stop with the selection: Invalid selection range also returned for the case: function foo() {
// bla/**/
} |
Few more cases may need to consider with explicit separate rules:
|
@mjbvz @jrieken I just found a case where adding VS Code interlacing its own expansions (specifically the one where it wants to select a full line) produces a pretty weird sequence: You can see the expansion where I would suggest for VS Code to:
|
I have a general fix for this in vscode that prevents any line selection range that isn’t fully contained by the next range in the list from being added. Will add a test after lunch and PR. |
Background
VS Code has proposed a smart selection API (microsoft/vscode#63935) that allows language extensions to intelligently control how selections are expanded and collapsed.
Just as an example, consider a simple class:
If the user starts with their cursor in the
a
ina === b
expression, progressively runningexpand selection
may result in the following selections:a
a === b
bar
methodbar
method declarationFoo
classThe exact selections we want may differ.
You can find the current state of the VS Code API proposal in
vscode.proposed.d.ts
under the nameselection range provider
Proposal
I think we should investigate supporting smart select for JavaScript and TypeScript users, but do so in a cross editor compatible way. The current proposal is derived from VS Code's needs so we should make sure it will work for other editors too
The current VS Code smart selection API proposal takes a position in a document and returns a list of potential expansion ranges. Each returned range also has some metadata about the range's kind (class, expression, statement, interface, ...)
On the TS Server side, a possible API would be:
We also need to determine which selection ranges we should target for a first iteration. A few basic ones:
/cc @DanielRosenwasser, @jrieken, @amcasey
The text was updated successfully, but these errors were encountered: