-
Notifications
You must be signed in to change notification settings - Fork 332
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
Fixing symbols hierarchy #34
Conversation
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.
@@ -29,19 +30,25 @@ class SymbolIndexer( | |||
|
|||
def documentSymbols( | |||
path: RelativePath | |||
): Seq[(Position.Range, Denotation)] = | |||
): Seq[SymbolInformation] = { |
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.
👍
denotation.name, | ||
denotation.symbolKind, | ||
path.toAbsolute.toLocation(position), | ||
Option(denotations.get(symbol.owner)).map(_.name) |
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.
How about using symbol.owner.syntax
? .owner
is likely a Symbol.Global
which has a .signature.name
we can use
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.
See #33 (comment). Also I didn't get you about .signature.name
.
Regarding broken nested hierarchy: facebookarchive/atom-ide-ui#121 |
@olafurpg how can we get the range of symbol's definition body? i.e. class/method/etc. with everything inside the braces |
@laughedelic you mean the source file positions? You would have to parse the file and find the tree node that defines that symbol and then use
|
Yes. Location range where the symbol starts and where its definition ends.
There are two things:
Sounds like too much extra work for this basic feature. I hoped that it's something that's already available in semanticdb, but just needs some digging..
This is a good idea for a workaround. I'll see how to do it. |
I agree it would be nice to support it. I believe eventually also VSCode will support a tree outline |
Also, apparently some extensions are already providing it. E.g. https://marketplace.visualstudio.com/items?itemName=patrys.vscode-code-outline |
Nice! And it also relies on nested ranges. It even selects the region when you click on the symbol. |
Yep, I've tested it with TypeScript and it works quite well! |
Cool! Let's do this then 💪 parsing the tree and finding the tree node of the symbol definition should not be too hard, which message needs this information? Rough sketch import scala.meta._, contrib._
val code: String = ???
def findSymbol(pos: Position) = ???
code.parse[Source].get.collect {
case node @ Defn.Object(_, name, _, _) =>
findSymbol(name.pos) -> node.pos
// ... same for Defn.Trait and Defn.Class
} |
@olafurpg thanks! I'll try to implement it. I was just concerned about reparsing source on every such request. |
Parsing is cheap, we use the parser to index all the *.scala source files of your dependencies at 30k LOC/s 😄 How often is this message called? |
I have to check, but I think it's called every time you open the outline panel and if it's open, on every file change 😓 |
We actually don't even need the compiler for this, once #23 is merged we can use the ctags indexer to build the semanticdbs on the fly. It's not prohibitive to run on every file change IMO, and if it turns out to be slow we can probably optimize it somehow. I agree this is a useful feature! |
Its containerName field should be the symbol.owner's name, not its type
It searches a Member node in the parsed tree that has the same name and same startLine. This widens position range to the symbol definition, which is necessary for correct symbols hierarchy nesting.
379a735
to
d471baa
Compare
I rebased this branch on master (after #23 was merged) and here's what I got
Notice that I didn't want to nest everything under the package symbol, because it's always top-level and wouldn't give you any additional information. Even with more than one package in the same file it's fine, because they kind of work like "separators". This is discussable, of course. About implementation: after some experimenting, I figured out how to do it using only parsing and traversing the tree. It's fast and updates immediately when you're editing (btw, how do you guys make those nice GIFs?). It was a good exercise for me to familiarize myself with the Unsolved problem: requests are sent on edits and responses are immediate, so when you edit to a non-parseable state, the outline is empty. This could be solved by storing the last generated symbol list and sending it until it can be updated. Any better ideas? |
Wow, that looks great! I personally use RecordIt for the gifs, but I think there's many options available. |
@gabro thanks, I will try it. |
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.
Nice work! Falling back to last outline makes sense, you can probably get away with a regular map keyed by the uri instead of using Observable.
Some(name) | ||
case Term.Select(qual, name) => | ||
qualifiedName(qual).map { prefix => s"${prefix}.${name}" } | ||
case Pkg(sel: Term.Select, _) => |
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.
What about package a
?
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.
It falls into the Member
case, so it will be just its name
a
.
This whole qualifiedName
method is here only for the packages with Select
, because their default name
is just the last part and I think it's useful to see the full name in the outline.
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 see, nice!
qualifiedName(qual).map { prefix => s"${prefix}.${name}" } | ||
case Pkg(sel: Term.Select, _) => | ||
qualifiedName(sel) | ||
case m: Member => |
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.
Glad you found this! Took me a while to learn about it :)
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.
What do you mean? 😅
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 wasn't aware of Member
until quite recently, always found it frustrating to handle all val/val/def/class/trait/object cases
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.
Ah, yes, it's very helpful. I just searched for whatever with name
, when I wanted to match it with symbols from the indexer 😄
@laughedelic I use GIPHY on OSX. |
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 just realized this is not too different from the recently added ScalaCtags https://github.com/scalameta/language-server/blob/8bbef8eb20e4cef9c8586cecf33b0093a09c6905/metaserver/src/main/scala/scala/meta/languageserver/ctags/ScalaCtags.scala
The ctags indexers will return a more useful schema after #35 which will probably make it more convenient to re-use for features like outline.
However, this PR is immediately useful and the feature looks really cool! I will keep my eyes open for sharing the implementation between ctags and outline if possible later down the road, but we should not let it block us now.
LGTM 👍 Nice work @laughedelic ! Feel free to merge.
val contents = buffers.read(path) | ||
for { | ||
tree <- contents.parse[Source].toOption.toList | ||
node <- tree.collect { |
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.
FYI scala.meta.contrib._
adds a .filter
on Tree
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.
Thanks, I was looking for it but found after I wrote this and then forgot to change it.
@olafurpg thanks for the review, but please don't merge it yet. I tested it today with some real projects and found a couple of corner-cases which should be handled more carefully. |
@laughedelic what do you think about merging this and addressing the corner cases in a separate issue/PR? The more this PR stays open the more conflicts we risk to introduce. |
I agree we should merge this to avoid conflicts with #37 👍 |
OK, I just pushed a couple of little fixes and I'm going to merge it. I'll submit another PR later with other improvements. |
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.
🎉
case Defn.Object(_) => SymbolKind.Namespace | ||
case Pkg.Object(_) => SymbolKind.Module | ||
case Defn.Object(_) => SymbolKind.Module | ||
case Pkg.Object(_) => SymbolKind.Namespace |
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.
Strictly speaking, package object are modules like regular objects. But having a different icon in the UI to distinguish package objects from objects makes sense.
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.
Yep, that's what I thought
Nice! This PR fixes one of the TODOs in #37 😄 |
I didn't have time yet to check all the big changes in #37, but I just noticed that merging this introduced some merge conflicts there |
I've fixed those, the conflicts were only related to document symbol provider which I had commented out |
yay! Nice job @laughedelic! |
WIP fix for #33.
Here's how it looks with 379a735:
The problem is, it shows only one level of nesting, which is wrong. I compared the responses with other servers (TypeScript and Python) and didn't find any difference:
containerName
just matches the corresponding symbol'sname
field, nothing special.