lexical-lsp / lexical

Lexical is a next-generation elixir language server
888 stars 82 forks source link

Respect existing capitalization in defmodule completion #819

Open mat-hek opened 3 months ago

mat-hek commented 3 months ago

closes #786

zachallaun commented 2 months ago

Thanks for the PR! Sorry about the delay in reviewing it -- I think everyone has been in a bit of a holding pattern after the announcement of the combined language server effort. But I think we should move on this and not let it languish!

I can't fully review this right now, but I did have one thought:

What if, instead of looking for the longest existing prefix, we looked at existing capitalization on a per-segment basis? For instance, imagine you have a Foo.HTTP and now you're adjusting capitalization for Bar.Http. Currently, it wouldn't be adjusted because of the prefix match. However, we could store a map of segments, so it would have %{"foo" => "Foo", "http" => "HTTP"} and then you just iterate over the list of segments in the module you're adjusting and do something like Map.get(case_map, lower_segment, original_segment).

There may be some edge cases I've not thought of, and we'd probably want to limit the case map to only modules in the current application as opposed to those in all dependencies. Thoughts? @scohen?

mat-hek commented 2 months ago

What if, instead of looking for the longest existing prefix, we looked at existing capitalization on a per-segment basis?

Good idea. Currently, having Foo.HTTP we'd also adjust Foo.Httpserver -> Foo.HTTPServer. To preserve this, we can do per-prefix matching on the segments ;)

zachallaun commented 2 months ago

What if, instead of looking for the longest existing prefix, we looked at existing capitalization on a per-segment basis?

Good idea. Currently, having Foo.HTTP we'd also adjust Foo.Httpserver -> Foo.HTTPServer. To preserve this, we can do per-prefix matching on the segments ;)

Cool! Maybe the "existing capitalizations" map can store title case segments, so having an existing Foo.HTTPServer would store %{"foo" => "Foo", "http" => "HTTP", "server" => "Server"}. It looks like the following regex does the job of correctly splitting title case, then the operation just becomes a nested map where we map over module segments split on . and then over title case segments:

r = ~r"""
# last char was lower and next char is upper:
# FooBAR
#   ^
(?<=[a-z])(?=[A-Z])

# or
|

# last char was upper and next chars are upper followed by lower:
# FOOBar
#   ^
(?<=[A-Z])(?=[A-Z][a-z])
"""x

[
  "FooBarBaz",
  "FOOBarBaz",
  "FooBARBaz",
  "FooBarBAZ",
  "foobarbaz",
  "A",
  "AB",
  "Ab",
  "AbC",
  "aBC"
]
|> Enum.each(fn name ->
  name
  |> String.split(r)
  |> IO.inspect()
end)
mat-hek commented 1 month ago

So, I've been quite busy recently and it's not going to change in October, so it'll probably be a while till I sit to this again :( So if anyone would like to take it over, don't hesitate, just let me know ;) Or I'll get back to it when I'm available