Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Use languageserver-types crate #79

Merged
merged 25 commits into from
Nov 23, 2016
Merged

Use languageserver-types crate #79

merged 25 commits into from
Nov 23, 2016

Conversation

bruno-medeiros
Copy link
Contributor

See #52

Of note, need to review lsp_data::to_usize and ActionHandler::on_change when range is None

@bruno-medeiros
Copy link
Contributor Author

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 = &params.textDocument.file_name();
let file_path = &uri_string_to_file_name(&params.text_document.uri);
Copy link
Member

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?

Copy link
Contributor Author

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 &params.text_document as an argument?

Copy link
Member

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.

Copy link
Contributor

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 ?

Copy link
Contributor Author

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 = &params.text_document.file_name().unwrap();

Copy link
Contributor

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?

Copy link

@Marwes Marwes Oct 28, 2016

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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 {
Copy link
Member

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 });
Copy link
Member

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),
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor

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.

Copy link
Contributor

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),
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link

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.

Copy link
Member

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.

Copy link

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.

Copy link
Contributor Author

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> .

Copy link
Member

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?
}
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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 {
Copy link
Member

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: ----------------- */
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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 {
Copy link
Member

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 ?
Copy link
Member

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?

Copy link
Contributor Author

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)

Copy link
Member

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).

@nrc
Copy link
Member

nrc commented Oct 26, 2016

Test failures look legit from Travis. Missing capabilities in the init reply?

@Arnavion
Copy link
Contributor

Arnavion commented Oct 26, 2016

I ran the tests locally and some fail, but I think it's because it's a Windows machine...

Tests should pass on Windows as well. Make sure you set RUST_TEST_THREADS=1 and SYS_ROOT=$(rustc --print sysroot) env vars.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@bruno-medeiros
Copy link
Contributor Author

Some thoughts:

  • For the new util module, is there a way to not have to duplicate the use statements from parent module?
  • I've changed rustls to use Position ctor, should I also use the newly added ctors for other basic LS types like Range, Diagnostic, etc. ?
  • Is there a plan to have URI parsing into the standard lib? Seems like this would be useful for Rust in general, and for here specifically, it would allow to move some URI relate ctors into languageserver-types crate itself.

@nrc
Copy link
Member

nrc commented Oct 27, 2016

For the new util module, is there a way to not have to duplicate the use statements from parent module?

No

I've changed rustls to use Position ctor, should I also use the newly added ctors for other basic LS types like Range, Diagnostic, etc. ?

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.

Is there a plan to have URI parsing into the standard lib? Seems like this would be useful for Rust in general, and for here specifically, it would allow to move some URI relate ctors into languageserver-types crate itself.

Not that I know of.

results.map(|comp| new_completion_item(
comp.matchstr.clone(),
comp.contextstr.clone(),
)).collect()
Copy link

@Marwes Marwes Oct 28, 2016

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()
}

@bruno-medeiros
Copy link
Contributor Author

In my opinion, since there will always be differing opinions on which methods to add to the types I'd prefer to have no methods or just the bare minimum number of methods lest the API is likely going to end up bloated by "wouldn't it be nice if..." methods.

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.

(I am a bit skeptic towards adding the constructor methods actually as they will basically be redundant once rust-lang/rust#37340 is implemented and gets to stable).

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... 😅

@bruno-medeiros
Copy link
Contributor Author

URI stuff up: gluon-lang/lsp-types#3
@nrc @Marwes feedback welcome.

Also note my conclusions on the URL/URI ambiguity:

"
This library uses the URL crate for parsing URIs.
Note that there is some confusion on the meaning of URLs vs URIs:
http://stackoverflow.com/a/28865728/393898 .
According to that information, on the classical sense of "URLs", "URLs" are a subset of URIs,
But on the modern/new meaning of URLs, they are the same as URIs.
The important take-away aspect is that the URL crate should be able to parse any URI,
such as urn:isbn:0451450523
"

@bruno-medeiros
Copy link
Contributor Author

Any outstanding requests/comments for this PR? It gets a bit hard to track in Github after many changes are added.

@nrc nrc mentioned this pull request Nov 11, 2016
let root_path = match root_path {
Some(some) => some,
None => return
};
Copy link
Member

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 }
}
};
Copy link
Member

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
Copy link
Member

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),
Copy link
Member

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>> {

Copy link
Member

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());
}
Copy link
Member

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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer FIXME to XXX

Copy link
Member

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
Copy link
Member

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: ----------------- */
Copy link
Member

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?

@bruno-medeiros
Copy link
Contributor Author

Could you add that to the comment please?

What do you mean? Add "These are not LSP types" message to the doc of CompilerMessageCode and CompilerMessage?

@bruno-medeiros
Copy link
Contributor Author

What do you mean? Add "These are not LSP types" message to the doc of CompilerMessageCode and CompilerMessage?

It might be preferable to just move those types elsewhere (maybe action.rs?), and keep lsp_data.rs with just LSP related stuff.

@nrc
Copy link
Member

nrc commented Nov 16, 2016

What do you mean?

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"

@nrc
Copy link
Member

nrc commented Nov 23, 2016

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.

@nrc nrc merged commit d134eee into rust-lang:master Nov 23, 2016
@matklad
Copy link
Member

matklad commented Nov 24, 2016

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).

@bruno-medeiros
Copy link
Contributor Author

Added #110 to fix the Cargo.lock and tests

@matklad
Copy link
Member

matklad commented Nov 24, 2016

Thanks!

@liamdawson liamdawson mentioned this pull request Oct 7, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants