Skip to content
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

Scala support #399

Merged
merged 31 commits into from
Jan 8, 2022
Merged

Scala support #399

merged 31 commits into from
Jan 8, 2022

Conversation

SCdF
Copy link
Contributor

@SCdF SCdF commented Dec 17, 2021

Basic Scala support. Tested with Scala 2.x syntax, idk enough about 3.x to know how it fares.

Relies on: cursorless-dev/vscode-parse-tree#16

@SCdF
Copy link
Contributor Author

SCdF commented Dec 17, 2021

(putting this out there so people know it's happening, also because I can't run tests locally in WSL so this gives me CI)

src/languages/scala.ts Outdated Show resolved Hide resolved
@SCdF
Copy link
Contributor Author

SCdF commented Dec 21, 2021

Also @pokey I'm guessing the tests are failing ("do not support plaintext") is because the version of vscode-parse-tree that's out there doesn't yet support scala?

@SCdF SCdF marked this pull request as ready for review December 21, 2021 14:46
Copy link
Member

@pokey pokey left a comment

Choose a reason for hiding this comment

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

Really good stuff! Left some inline comments. Also, it would be great to see the following tests:

  • Tests for "type" where the mark is not in the type itself. I don't know scala enough to say what situations would be comprehensive, but here are a couple that jump out at me:
    • Have the mark in the value / body of a function and say "clear type" to see that it clears the return type of the function
    • Have the mark in the name of a function call argument and say "clear type" to see that it clears the type of the function call argument
  • Similarly, tests for "value" where the mark is not in the value itself, eg it's in the name / type of the argument or assignment
  • A check for "condition" where the mark is not in the condition itself
  • "take round" with a document like so: "(hello)", and cursor in the middle of hello to check that the text fragment extractor is working

src/languages/scala.ts Outdated Show resolved Hide resolved
src/languages/scala.ts Outdated Show resolved Hide resolved
src/languages/scala.ts Outdated Show resolved Hide resolved
// Pulled from the complete list that isn't implemented above

// collectionItem: "???"
// collectionKey: "???",
Copy link
Member

Choose a reason for hiding this comment

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

Don't you want to support "AL" -> "Alabama" expressions? For these, "key" would be "AL", "value" would be "Alabama", and "item" would be the pair.

collectionItem is also supposed to include list elements, tho since those are just function calls you would basically use an argument matcher and check that the parent is a List. For fancy stuff like this, you might check out the Clojure implementation. That one took tons of custom matchers to get working

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'd have to write something custom for that: ("AL" -> "Alabama") comes out as:

          arguments: (arguments [1, 19] - [1, 38]
            (infix_expression [1, 20] - [1, 37]
              left: (string [1, 20] - [1, 24])
              operator: (operator_identifier [1, 25] - [1, 27])
              right: (string [1, 28] - [1, 37]))))))))

So I'd need to take the left or right of an operator, when that operator === '->'

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that looks like it should be doable, but a bit custom. I think I'd be ok with shipping without it for now but filing an issue to capture all the missing things. I believe cursorless will generate a helpful message so that users can just see it hasn't been implemented yet

Copy link
Member

Choose a reason for hiding this comment

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

btw I'd capture your example here into the follow-up issue for remaining scala features; it's quite helpful

// regularExpression: "???",

// attribute: "???",
// xmlElement: "???",
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't scala support inline html elements?

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 does (well, XML), but it's deprecated and will be dropped in 3.x, to be replaced with string formatting (I think, I don't know much about 3.x). I'm not sure how much people use it. The tree sitter implemention also errors with it.

docs/contributing/adding-a-new-language.md Show resolved Hide resolved
Testing when the mark is on the name of the value.
For marks which aren't the condition itself
@SCdF
Copy link
Contributor Author

SCdF commented Jan 6, 2022

On types, given:

class Example(foo: String) {
  def str(bar: String): String = foo + bar
}

We get:

(compilation_unit [0, 0] - [3, 0]
  (class_definition [0, 0] - [2, 1]
    name: (identifier [0, 6] - [0, 13])
    class_parameters: (class_parameters [0, 13] - [0, 26]
      (class_parameter [0, 14] - [0, 25]
        name: (identifier [0, 14] - [0, 17])
        type: (type_identifier [0, 19] - [0, 25])))
    body: (template_body [0, 27] - [2, 1]
      (function_definition [1, 2] - [1, 42]
        name: (identifier [1, 6] - [1, 9])
        parameters: (parameters [1, 9] - [1, 22]
          (parameter [1, 10] - [1, 21]
            name: (identifier [1, 10] - [1, 13])
            type: (type_identifier [1, 15] - [1, 21])))
        return_type: (type_identifier [1, 24] - [1, 30])
        body: (infix_expression [1, 33] - [1, 42]
          left: (identifier [1, 33] - [1, 36])
          operator: (operator_identifier [1, 37] - [1, 38])
          right: (identifier [1, 39] - [1, 42]))))))

From what you're saying it looks like we want to walk up the tree to find a sibling type, and take the parent. For example, if your cursor was in class_parameter.name (foo) and you said "take type" it should find class_parameter.type (String) and then match class_parameter (foo: String)?

How would I write this? And how would I merge this with the leading matcher I'm already using that I obviously do not want to use in this case.

@SCdF
Copy link
Contributor Author

SCdF commented Jan 6, 2022

On "take round", this does not work ("TypeError: Cannot read property 'endPosition' of undefined"). Any hints as to how to fix this?

@SCdF
Copy link
Contributor Author

SCdF commented Jan 6, 2022

(value and condition work fine, and I added tests)

@pokey
Copy link
Member

pokey commented Jan 6, 2022

On types, given:

class Example(foo: String) {
  def str(bar: String): String = foo + bar
}

We get:

(compilation_unit [0, 0] - [3, 0]
  (class_definition [0, 0] - [2, 1]
    name: (identifier [0, 6] - [0, 13])
    class_parameters: (class_parameters [0, 13] - [0, 26]
      (class_parameter [0, 14] - [0, 25]
        name: (identifier [0, 14] - [0, 17])
        type: (type_identifier [0, 19] - [0, 25])))
    body: (template_body [0, 27] - [2, 1]
      (function_definition [1, 2] - [1, 42]
        name: (identifier [1, 6] - [1, 9])
        parameters: (parameters [1, 9] - [1, 22]
          (parameter [1, 10] - [1, 21]
            name: (identifier [1, 10] - [1, 13])
            type: (type_identifier [1, 15] - [1, 21])))
        return_type: (type_identifier [1, 24] - [1, 30])
        body: (infix_expression [1, 33] - [1, 42]
          left: (identifier [1, 33] - [1, 36])
          operator: (operator_identifier [1, 37] - [1, 38])
          right: (identifier [1, 39] - [1, 42]))))))

From what you're saying it looks like we want to walk up the tree to find a sibling type, and take the parent. For example, if your cursor was in class_parameter.name (foo) and you said "take type" it should find class_parameter.type (String) and then match class_parameter (foo: String)?

How would I write this? And how would I merge this with the leading matcher I'm already using that I obviously do not want to use in this case.

Pretty sure you can just define a pattern matcher "*[type]"

@pokey
Copy link
Member

pokey commented Jan 6, 2022

On "take round", this does not work ("TypeError: Cannot read property 'endPosition' of undefined"). Any hints as to how to fix this?

Yeah probably scala isn't creating child nodes for quotes. I'd try to steal Java's text fragment extractor

You can now grab types when the mark is not in the type itself
@SCdF
Copy link
Contributor Author

SCdF commented Jan 6, 2022

OK I added code / tests for types, and used the hacked text fragment extractor and it looks to work correctly

I'm not sure we can ever truly detect these. At least in the current tree sitter implementation they are eg just infix expressions, just like lots of other things
src/languages/scala.ts Outdated Show resolved Hide resolved
@SCdF
Copy link
Contributor Author

SCdF commented Jan 8, 2022

Raised #434 to talk about / track pattern matching

Copy link
Member

@pokey pokey left a comment

Choose a reason for hiding this comment

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

This looks great! 👏👏

src/languages/scala.ts Show resolved Hide resolved
src/languages/scala.ts Show resolved Hide resolved
// Cursorless terminology not yet supported in this Scala implementation
/*
lists and maps basic definition are just function calls to constructors, eg List(1,2,3,4)
These types are also basically arbitrary, so we can't really hard-code them
Copy link
Member

Choose a reason for hiding this comment

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

Why can't we hard-code them? Not saying you need to do it in this PR, but this comment implies that we can't do it, whereas I thought we were just punting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are just classes, not a syntax / language construct like [] in JS or whatever. Scala comes with a bunch of them but people will also use other random libraries 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.

Interesting. Almost feels like we need LSP support for this one? Let's just call it out in the missing language features issue and discuss there

// Pulled from the complete list that isn't implemented above

// collectionItem: "???"
// collectionKey: "???",
Copy link
Member

Choose a reason for hiding this comment

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

btw I'd capture your example here into the follow-up issue for remaining scala features; it's quite helpful

@pokey pokey merged commit 8e7f101 into cursorless-dev:main Jan 8, 2022
@pokey pokey mentioned this pull request Jan 9, 2022
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants