-
Notifications
You must be signed in to change notification settings - Fork 256
Conversation
Note that a case of DidChangeTextDocumentParams need handling. Closes rust-lang#52
I ran the tests locally and some fail, but I think it's because it's a Windows machine... |
@@ -212,7 +228,7 @@ impl ActionHandler { | |||
let vfs: &Vfs = &self.vfs; | |||
let result: Vec<CompletionItem> = panic::catch_unwind(move || { | |||
let pos = adjust_vscode_pos_for_racer(params.position); | |||
let file_path = ¶ms.textDocument.file_name(); | |||
let file_path = &uri_string_to_file_name(¶ms.text_document.uri); |
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.
Could we reduce the code dup here?
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 code dup? You mean accessing .uri
? As in, do you want a function that only takes ¶ms.text_document
as an argument?
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.
Perhaps a macro that works on anything with a uri field (or even a textDocument field, depending on what the other uses are like)? Not the end of the world if not. I guess it is not much code dup, it just looks like a bit more complicated than the old version.
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.
Alternatively have a FileUri newtype that impls Deserialize to convert a string to a PathBuf via Url::to_file_path ?
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.
Hum, I think the code dup here is minimal, insignificant almost. If anything, what was nicer about the original version was that it was an method of the LSP type, instead of a free function.
I think maybe we could move it back to a method if we introduce a dependency on the URL crate in languageserver-types
, @Marwes what do you think of that? I think any LS would end up needing URL/URL parsing anyways, so it seems ok to me.
The rustls code would then be like this:
let file_path = ¶ms.text_document.file_name().unwrap();
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.
uri_string_to_file_name
already assumes it's going to be given a file://
URI that it can convert to a PathBuf
. What else could that field contain and how would rls be expected to handle 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.
I just meant that the uri
field should not be restricted to file://
, it is perfectly fine for rls
to just handle file://
but in general it could be an identifier for something else.
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.
@Arnavion remember that languageserver-types (and RustLSP too BTW) is meant to be usable by servers other than just rustls (indeed there is https://github.com/gluon-lang/gluon_language-server , and I also hope to use languageserver-types in another project) . So even if rustls only support file://
resource, languageserver-types should support any valid LSP usage.
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.
So, I'm happy to keep this as is, since it seems there is no universally better solution. Do we want to change these fields to Uri
upstream? Before landing this?
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.
@nrc Yeah I'm up for adding that, although ATM I'm getting bogged down in the confusion of URLs vs. URIs 😖
pub fn on_change(&self, change: DidChangeTextDocumentParams, out: &Output) { | ||
let fname: PathBuf = Url::parse(&change.text_document.uri).unwrap().to_file_path().unwrap(); | ||
let changes: Vec<Change> = change.content_changes.iter().map(move |i| { | ||
let range = match i.range { |
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.
Please use spaces, not tabs
@@ -370,16 +386,17 @@ impl ActionHandler { | |||
|
|||
let mut contents = vec![]; | |||
if !docs.is_empty() { | |||
contents.push(MarkedString { language: "markdown".into(), value: docs }); | |||
contents.push(MarkedString::LanguageString { language: "markdown".into(), value: 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.
Could this get a ctor function?
@@ -61,8 +62,8 @@ enum Method { | |||
|
|||
#[derive(Debug)] | |||
enum Notification { | |||
CancelRequest(usize), | |||
Change(ChangeParams), | |||
CancelRequest(NumberOrString), |
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.
Is this an id? I think there is another PR that uses Id
rather than NumberOrString, it would be good to rebase on top of that
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.
Yeah, it's an id. (Well, one that is not null
at least. Tecnically in JSON-RPC null
is allowed as an id, but discouraged)
But, if we want to use the same type, the other PR would have to use the languageserver-types
type, not the other way around, because that would require adding a dependency on rustls
to languageserver-types
, which would not be suitable.
Anyways, it's likely that Id
will be replaced eventually, if/when a PR comes to use RustLSP or another JSON-RPC library.
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.
ok cc #73 and @craftytrickster
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 haven't had time yet to make the test changes you wanted, which is why it has been a bit stagnant. I will close it and just make the PR on top of this crate instead.
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 that bruno already added the deserializaton for NumberOrString, I guess there's no need for me to make any changes.
resolveProvider: true, | ||
triggerCharacters: vec![".".to_string()], | ||
}, | ||
text_document_sync: Some(TextDocumentSyncKind::Incremental), |
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.
There seem to be a lot more Options all over the place, that seems unfortunate - why is that?
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's just the way the LSP protocol is modelled. Arguably the protocol itself could be simplified in this regard, but otherwise I think we should model the protocol types precisely (even if it has redundancies). That is not to say we couldn't create a shortcut ctor function, or even an intermediate type (say, to ServerCapabilities
), that would not have the Options. I'll give that a try.
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.
Where there is an Option could you add a null case to the enum and ensure it gets serialised as 0 or null or whatever the protocol wants?
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.
@nrc What do you mean, do you want it to serialize to the JSON "text_document_sync":null
if text_document_sync field is None? That be achieved if we remove the #[serde(skip_serializing_if="Option::is_none")]
attributes.
I've brought up the issue of null vs missing properties (undefined in JSON terms) with LSP guys here microsoft/language-server-protocol#87 and argued that they should be semantically equivalent and the protocol should reflect that.
But serializing to null might be clearer, yeah.
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.
Where there is an Option could you add a null case to the enum and ensure it gets serialised as 0 or null or whatever the protocol wants?
The enum itself is not defined with a "null" case so adding one to the enum would just be more confusing it kind of smells like a null pointer.
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 mean instead of having
enum Foo {
Bar,
Baz,
}
and using Option<Foo>
, you have:
enum Foo {
Bar,
Baz,
None, // or Null or Missing or whatever
}
and use Foo
directly.
smells like a null pointer.
There is nothing wrong with null pointers, the problem is when they are implicit and thus un-tested. With this pattern, they are just as explicit and thus safe to 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.
the problem is when they are implicit and thus un-tested. With this pattern, they are just as explicit and thus safe to use.
That is part of it (and the most troublesome part), but I'd argue that adding an extra value just because it is possible and simplifies some parts is similar to how null pointer got so prevalent as well.
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.
Ugh, I also am quite averse to that suggestion of adding None/Missing variants. Feels very wrong: it makes it impossible to specify a non-null Foo
, which I think is an inferior abstraction. I don't think expressive power should be sacrificed just so that we save some minor boilerplate code when matching against Option<Foo>
.
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 guess it depends on what you are modelling - if there is a case for a non-null Foo, then I would agree, if the Foo intrinsically has a null case then I think it is better to reflect that in the enum. Does the spec always allow null for these enums? If so I'd argue that using Option rather than extending Foo is unnecessary complexity (YAGNI). If there are places in the spec where you are allowed non-null, then keep them as is.
|
||
pub fn from_usize(pos: usize) -> u64 { | ||
TryFrom::try_from(pos).unwrap() // XXX: Should we do error handling or assume it's ok? | ||
} |
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.
SHould just use a cast rather than a function for this.
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.
You sure? It would silently overflow/truncate instead of panicking... of course this would only matter for 128-bit architectures... 😅
pub version: u64, | ||
pub uri: String | ||
} | ||
pub type HoverParams = TextDocumentPositionParams; |
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.
Can probably do without this
#[derive(Debug, Deserialize)] | ||
pub struct TextDocumentIdentifier { | ||
pub uri: String | ||
pub fn new_completion_item(label: String, detail: String) -> CompletionItem { |
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.
Could this have a more descriptive name? It is not really a CompletionItem ctor fn
pub kind: u32, // can be made to numeric enum | ||
pub location: Location, | ||
} | ||
/* ----------------- These are not LSP types: ----------------- */ |
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 comment doesn't seem helpful
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.
The comment is a reflection that those types should probably be moved elsewhere (I guess it's more of a TODO). But I don't know where.
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.
Could you add that to the comment please?
#[derive(Debug, Deserialize)] | ||
pub struct Document { | ||
pub uri: String | ||
pub fn sk_from_def_kind(k: raw::DefKind) -> SymbolKind { |
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.
s/sk/symbol_kind
@@ -74,6 +74,7 @@ pub fn parse_string(input: &[u8]) -> Result<String, serde_json::Error> { | |||
serde_json::from_str(&s) | |||
} | |||
|
|||
// TODO: deprecate/remove in favor of ls_types::SymbolKind ? |
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.
Is it still used anywhere?
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, in actions_http::Symbol and further up by the HTTP server. The serge::Serialize implementation is completely different than the LS type BTW, so it would not even be compatible to replace it with the LS type. (unless the HTTP client is changed as well)
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.
OK, could you add to the TODO that it is only used by the http server please? (We plan to remove the http mode soon-ish).
Test failures look legit from Travis. Missing capabilities in the init reply? |
Tests should pass on Windows as well. Make sure you set Edit: ... and, for the moment, that the project you test this on is in a path without spaces. |
pub struct Position { | ||
pub line: usize, | ||
pub character: usize, | ||
pub fn uri_string_to_file_name(uri: &str) -> PathBuf { |
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.
👍
Conflicts: src/ls_server.rs
Some thoughts:
|
No
Yeah, I would use those ctors where it makes sense (i.e., where it makes the code nicer). I wouldn't say to do it all the time, necessarily.
Not that I know of. |
results.map(|comp| new_completion_item( | ||
comp.matchstr.clone(), | ||
comp.contextstr.clone(), | ||
)).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.
Alternatively
CompletionItem {
label: comp.matchstr.clone(),
detail: comp.contextstr.clone(),
.. CompletionItem::default()
}
Yes, there will be different opinions on which utility methods to add to types, but I don't think that is a justification for doing just the bare minimum. Yes, the bare minimum is a much more objective metric for measuring inclusion rather than how "useful" or "common" a utility method is in used in 3rd party code, but again I don't think that justifies doing the bare minimum. If a method might be "useful" for 3rd party developers (and is general enough, and has minimal maintenance overheard), I think it's worth including. Yes, with this approach you have the problem of deciding if an utility method is "useful" enough, which is a subjective and imprecise judgement. You can have some guidelines on that, but ultimately this is one of those things that is left to the judgement of a software craftsman. (the "art" in software engineering?) It's a bit like testing: Testing does not guarantee absence of bugs, so how do decide how much tests are enough? You can try to go for a certain coverage percentage for you component under test, but coverage % is not a precise measure of test quality, far from it. Ultimately you have to rely on your own judgement, there is no other way... Anyway, I don't want to get in a big philosophical debate here, suffice to say I prefer to err on the "bloated" side of things. We have namespaces, so what harm is there in adding utility methods to a type if someone is willing to write them? Again, assuming those methods have low maintenance overhead and are general enough - these are the only concerns I would have.
Can always remove them once that feature is in. For the record I should probably mention I don't usually follow Rust RFCs - I only learn about new languages features once they are released in Rust stable. I'm not that up to date... 😅 |
URI stuff up: gluon-lang/lsp-types#3 Also note my conclusions on the URL/URI ambiguity: " |
Conflicts: Cargo.lock
Conflicts: src/test/mod.rs
Conflicts: src/ls_server.rs src/lsp_data.rs
Conflicts: src/actions_ls.rs src/ls_server.rs
Conflicts: Cargo.toml src/actions.rs src/server.rs
Any outstanding requests/comments for this PR? It gets a bit hard to track in Github after many changes are added. |
let root_path = match root_path { | ||
Some(some) => some, | ||
None => return | ||
}; |
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 should be done by the caller, not here.
let end_pos = Position::new(0, 0); | ||
Range{ start : Position::new(0, 0), end : end_pos } | ||
} | ||
}; |
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.
Seems to like making a range for a whole file should be a util function
location: Location::from_span(&s.span), | ||
kind: source_kind_from_def_kind(s.kind), | ||
location: ls_util::location_from_span(&s.span), | ||
container_name: None // TODO: more info could be added here |
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.
nit: prefer FIXME over TODO for things being left in the code (i.e., no TODOs should get committed)
&session) | ||
.and_then(|mtch| { | ||
let source_path = &mtch.filepath; | ||
if mtch.point != 0 { | ||
let (line, col) = session.load_file(source_path) | ||
.point_to_coords(mtch.point) | ||
.unwrap(); | ||
Some(Location::from_position(source_path, | ||
Some(ls_util::location_from_position(source_path, | ||
adjust_racer_line_for_vscode(line), |
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.
nit: indents need adjusting
pub line: usize, | ||
pub character: usize, | ||
pub fn parse_file_path(uri: &Url) -> Result<PathBuf, Box<Error>> { | ||
|
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.
nit: blank line
|
||
if uri.scheme() != "file" { | ||
return Err("URI scheme is not `file`".into()); | ||
} |
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.
nit: prefer if ... == ... { ... } else { ... }
(no return)
/// End position is exclusive | ||
pub end: Position, | ||
pub fn from_usize(pos: usize) -> u64 { | ||
TryFrom::try_from(pos).unwrap() // XXX: Should we do error handling or assume it's ok? |
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.
prefer FIXME to XXX
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.
also, no, this will always succeed. And you can just use a cast here.
impl Range { | ||
pub fn from_span(span: &Span) -> Range { | ||
pub fn to_usize(pos: u64) -> usize { | ||
TryFrom::try_from(pos).unwrap() // FIXME: for this one we definitely need to add error checking |
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 use a cast, not TryFrom
pub kind: u32, // can be made to numeric enum | ||
pub location: Location, | ||
} | ||
/* ----------------- These are not LSP types: ----------------- */ |
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.
Could you add that to the comment please?
What do you mean? Add "These are not LSP types" message to the doc of |
It might be preferable to just move those types elsewhere (maybe |
Conflicts: Cargo.lock
I meant could you change the comment in the code from "these types are no LSP types" or whatever to something like "FIXME: these types are not LSP types and don't really belong in this module" |
Thanks for all the changes! I think this is ready to merge now. I believe the test failures are just due to compiler/Serde mismatches, so I'll merge. Praying it doesn't explode. |
Looks like it did 💥 #108 :) FYI, maintainers can restart Travis builds for PRs from the Travis UI, contributors can restart the build by just closing and reopening a PR (to clarify, you don't need to open a new pull request, you need just to click to buttons on the PR page). |
Added #110 to fix the Cargo.lock and tests |
Thanks! |
See #52
Of note, need to review
lsp_data::to_usize
andActionHandler::on_change
when range is None