-
-
Notifications
You must be signed in to change notification settings - Fork 369
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
support selection range lsp feature #2565
Conversation
25fc2cc
to
85d0ddb
Compare
Currently the selection range is generated directly from the structure of
For case 1, there isn't a single node in ast containing all import statements. For case 2, signatures and definitions are distinct ast nodes (and actually they are not necessarily be placed nearby in source file) If needed, special logic can be added to handle cases like this, but will incur extra complexity and compatibility issues (the actual structure of AST in GHC changes between versions?). Not sure whether to leave it as is, or handle the cases specially. |
55c2d5e
to
4567bf8
Compare
Recent progress:
Future work:
|
FWIW I think it would be fine to merge a first version without this and then add it later. Up to you when you consider that this has enough features! I'll try and do a |
Currenly all the planned work is done, except for bumping lsp version. Marking it ready for review and waiting for GitHub actions result now. |
great work, I guess a LSP release would be useful /cc @michaelpj a small demo exhibiting the feature would be great too |
A screen record: selection_range_demo.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks pretty good, could just do with some comments where there's cleverness happening. Might be good to get another look from @wz1000 for the various HIE AST manipulations.
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange.hs
Outdated
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange.hs
Outdated
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange.hs
Outdated
Show resolved
Hide resolved
go x (y:ys) = if x == y then y:ys else x:y:ys | ||
|
||
findSelectionRangesByPositions :: [SelectionRange] -> [Position] -> [SelectionRange] | ||
findSelectionRangesByPositions selectionRanges = fmap findByPosition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd maybe just define the un-fmaped version and fmap it at the use site, but 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function filters the [SelectionRange]
, preserving all SelectionRange
s containing at least one of the Positions
. (will add to comment later)
So, at first glance, I think there might exist better algorithm for this, at least better than the currently implemented simple traversal. That's why I keep it as a standalone function, rather than moving fmap to the caller site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't seem quite right (unless I'm reading it wrong). Rather, it goes through all the positions and finds (or creates) a selection range that contain it! So it's more like pickSelectionRangeForPosition
, fmaped over positions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelpj Thanks for reading it so carefully. You got it right, the previous description is not accurate. I added more detailed explaination in the comment.
The performance concern makes sense somehow. If someone selects hundreds of positions in a batch edit, for a file containing tens of thousands of leaf nodes, it might become slow quickly. And will block the user's editing.
It is probably too early to optimize for things like that, for the very first version of this plugin. But at least for the possible optimizations I spotted during writing code, I would like to leave proper space for them. i.e. making the related code grouped together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm yes, I hadn't even thought about performance. In theory this is O(n*m) for the two input lists, but TBH I agree that they're almost certainly small and it doesn't matter.
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange.hs
Outdated
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
Outdated
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
Outdated
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
Outdated
Show resolved
Hide resolved
Recent update:
Maybe it's ready for another review, to check if things are made right. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of nits, otherwise LGTM. Would still be nice to get a review from someone who knows about the HIE file stuff, but let's not block it on that.
plugins/hls-haddock-comments-plugin/test/testdata/test/testdata/QualFunction.expected.hs
Show resolved
Hide resolved
plugins/hls-selection-range-plugin/hls-selection-range-plugin.cabal
Outdated
Show resolved
Hide resolved
go x (y:ys) = if x == y then y:ys else x:y:ys | ||
|
||
findSelectionRangesByPositions :: [SelectionRange] -> [Position] -> [SelectionRange] | ||
findSelectionRangesByPositions selectionRanges = fmap findByPosition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm yes, I hadn't even thought about performance. In theory this is O(n*m) for the two input lists, but TBH I agree that they're almost certainly small and it doesn't matter.
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
Outdated
Show resolved
Hide resolved
From a new contributor's perspective, before reaching this point, most of the time was spent on figuring out how to use |
Many thanks for the observation, that is a ghc type, right? i wonder what could we do here to improve the knowledge about until ghc get better docs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, many thanks for including anew one whole feature!
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
Outdated
Show resolved
Hide resolved
Oh, it lives in package This is another topic though, but I think it would be helpful to have these information somewhere (or maybe it exists, but not found by me):
|
6a2a23a
to
2d3c3ec
Compare
CI is green now, maybe it's ready for a merge? |
Ping @wz1000 for this question
That's an interesting question. Definitely prefer
Ping @wz1000 for those questions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few more comments
plugins/hls-selection-range-plugin/src/Ide/Plugin/SelectionRange.hs
Outdated
Show resolved
Hide resolved
Performance Tips: | ||
Doing a linear search from the first selection range for each position is not optimal. | ||
If it becomes too slow for a large file and many positions, you may optimize the implementation. | ||
At least we don't need to search from the very beginning for each position, if the are sorted firstly. | ||
Or maybe we could apply some techniques like binary search? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please, do consider at least benchmarking this in a follow-up PR. We do care about performance! Unfortunately the benchmark suite doesn't currently support plugins (it's a benchmark suite for ghcide
, not HLS).
I wonder if binary search is really the solution here. I suspect HieAst
is isomorphic to a position indexed search tree, so the optimal approach would take advantage of this to find the solution without building a list of all selection ranges.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's another optimization I didn't come up with. So if I get it correctly, there are two known optimizations now. (let n represents the number of selection ranges, m represents the number of positions)
- For each position, we may treat HieAST as a position indexed tree to search it in O(log(n)).
- For all positions, a searched position will narrow the search range for other positions. If we search the middle position first, then all positions before this position may only need to search in half of the tree (if both the tree and positions are balanced)
If this looks right, I will update the comment to reflect these thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, at the moment (I think) you build all the possible selection ranges in the document, and then filter them by position, when you could fuse that into one pass and only build the paths which contain the target position or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's worth noting that, as selection ranges have shared parents, building up the list is O(n), where n is the number of nodes in the original AST. astPathsLeafToRoot
basically clone the tree and make every pointer reversed. And functions in ASTPreProcess
need to traverse through the AST, they are also O(n). So maybe this is not the only performance bottleneck, and we will need furthur benchmarks to make a conclusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that it matters much but I think laziness saves a 1/2 constant factor on average for building the root to leaf paths, if we have few positions. I think this holds true for the preprocessing phase too even with the reverse
foldl'
but not certain.
So if the preprocessing phase is lazy enough, then I think if we needed to upgrade the algorithm applying it to only astPathsLeafToRoot
might suffice.
Added another issue #2632 to track the performance optimization |
It seems that some recent changes to nix build files in master branch break this. Looking into that. |
Thanks again! |
this will fix #212
TODO
HieAST
to generate selection rangeslsp-types
version to master in both cabal and stack files8.10.7
, which I used during development)lsp-types
to a new version on hackage before this is merged