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

feat(language-service): add support for text replacement #33091

Closed
wants to merge 9 commits into from

Conversation

ayazhafiz
Copy link
Member

Adds a replacementSpan field on a completion that will allow typed
text to be replaced with the suggested completion value if a user
selects the completion. Previously, the completion value would simply be
appended to the text already typed. E.g. if we had

{{ti}}

typed in a template and title was recommended as a completion and
selected, the template would become

{{tititle}}

With replacementSpan, the original text ti will be replaced for
title.

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Feature

Does this PR introduce a breaking change?

  • Yes
  • No

@ayazhafiz ayazhafiz added feature Issue that requests a new feature area: language-service Issues related to Angular's VS Code language service target: major This PR is targeted for the next major release labels Oct 11, 2019
@ayazhafiz ayazhafiz requested a review from kyliau October 11, 2019 01:28
@ayazhafiz ayazhafiz self-assigned this Oct 11, 2019
@ngbot ngbot bot added this to the needsTriage milestone Oct 11, 2019
@ayazhafiz
Copy link
Member Author

This is WIP. I believe #31897 will need to land before this really works, because we are facing issues relating to invalid template AST sourceSpans (similar to what we saw, and still see, with the indexer module). In particular, I've included a test that fails with a BoundEvent template AST:

@Component({
  template: `<a href="https://angular.io" (click)="han"></a>`,
})
export class AngularLinkComponent {
  handleClick() {}
}

Generating completions for han in the bound "click" event will produce an incorrect replacementSpan because the recorded span of the BoundEvent AST is incorrect.

@ayazhafiz
Copy link
Member Author

@andrius-pra this may interest you as well.

@andrius-pra
Copy link
Contributor

Generating completions for han in the bound "click" event will produce an incorrect replacementSpan because the recorded span of the BoundEvent AST is incorrect.

Possible fix: You can add special case when node is ASTWithSource.

let closestAst !: AstPath<AST>;
if (ast instanceof ASTWithSource) {
   closestAst = findAstAt(ast.ast, templatePosition - ast.sourceSpan.start);
} else {
   closestAst = findAstAt(ast, templatePosition - templateAstTail.sourceSpan.start.offset);
}

@ayazhafiz
Copy link
Member Author

Generating completions for han in the bound "click" event will produce an incorrect replacementSpan because the recorded span of the BoundEvent AST is incorrect.

Possible fix: You can add special case when node is ASTWithSource.

let closestAst !: AstPath<AST>;
if (ast instanceof ASTWithSource) {
   closestAst = findAstAt(ast.ast, templatePosition - ast.sourceSpan.start);
} else {
   closestAst = findAstAt(ast, templatePosition - templateAstTail.sourceSpan.start.offset);
}

Good point. I'll try this out, but I think I did something similar in June and it wasn't very reliable. Thank you!

ayazhafiz added a commit to ayazhafiz/angular that referenced this pull request Oct 15, 2019
Prior to this commit, the absolute spans (relative to template source
file rather than the start of an expression) of expressions in a
template attribute like `*ngIf` were generated incorrectly, equating to
the relative spans.
This fixes the bug by passing an `absoluteOffset` parameter when parsing
template bindings.

Through some levels of indirection, this is required for the Language
Service to support text replacement in
angular#33091.
andrius-pra pushed a commit to andrius-pra/angular that referenced this pull request Oct 16, 2019
Prior to this commit, the absolute spans (relative to template source
file rather than the start of an expression) of expressions in a
template attribute like `*ngIf` were generated incorrectly, equating to
the relative spans.
This fixes the bug by passing an `absoluteOffset` parameter when parsing
template bindings.

Through some levels of indirection, this is required for the Language
Service to support text replacement in
angular#33091.

(cherry picked from commit 9e2b784)
matsko pushed a commit that referenced this pull request Oct 17, 2019
#33189)

Prior to this commit, the absolute spans (relative to template source
file rather than the start of an expression) of expressions in a
template attribute like `*ngIf` were generated incorrectly, equating to
the relative spans.
This fixes the bug by passing an `absoluteOffset` parameter when parsing
template bindings.

Through some levels of indirection, this is required for the Language
Service to support text replacement in
#33091.

PR Close #33189
ODAVING pushed a commit to ODAVING/angular that referenced this pull request Oct 18, 2019
angular#33189)

Prior to this commit, the absolute spans (relative to template source
file rather than the start of an expression) of expressions in a
template attribute like `*ngIf` were generated incorrectly, equating to
the relative spans.
This fixes the bug by passing an `absoluteOffset` parameter when parsing
template bindings.

Through some levels of indirection, this is required for the Language
Service to support text replacement in
angular#33091.

PR Close angular#33189
AndrusGerman pushed a commit to AndrusGerman/angular that referenced this pull request Oct 22, 2019
angular#33189)

Prior to this commit, the absolute spans (relative to template source
file rather than the start of an expression) of expressions in a
template attribute like `*ngIf` were generated incorrectly, equating to
the relative spans.
This fixes the bug by passing an `absoluteOffset` parameter when parsing
template bindings.

Through some levels of indirection, this is required for the Language
Service to support text replacement in
angular#33091.

PR Close angular#33189
ayazhafiz added a commit to ayazhafiz/angular that referenced this pull request Oct 24, 2019
Moves to using the absolute span of an expression AST (relative to an
entire template) rather than a relative span (relative to the start
of the expression) to find an expression AST given a position in a
template.

This is part of the changes needed to support text replacement in
templates (angular#33091).
AndrewKushnir pushed a commit that referenced this pull request Oct 25, 2019
…#33387)

Moves to using the absolute span of an expression AST (relative to an
entire template) rather than a relative span (relative to the start
of the expression) to find an expression AST given a position in a
template.

This is part of the changes needed to support text replacement in
templates (#33091).

PR Close #33387
@ayazhafiz
Copy link
Member Author

Okay, so half the problem seems to be solved -- we can now replace spans when performing a completion at the end of the text span! So in a case like {{ti}}, selecting a completion when typing after i will replace the entire span with the completion, like {{title}}.

Unfortunately, we cannot currently replace spans when selecting a completion in the middle of the span. For example, say we had already typed title and then were typing it again, starting from the front:

{{tititle}}
    ^ typing position 

We'd expect that selecting the completion title would replace everything in the span, or at least ti. But it won't, because the way we currently grab the span to replace is by subtracting the size of the current span from the position that we are at.

One way to resolve this is to go work on the Angular AST even more, and have completely atomic ASTs for every semantic word or symbol in an Angular program. This would be a major overhaul and would take a lot of time, as we would have to do it for both expression ASTs and markup (HTML) ASTs. I think this is the most correct solution, but maybe not the most practical.

The other option is to perform some kind of matching algorithm - maybe we can figure out where the span to replace starts but looking at the overlap with the completion, then erasing the entire word in the span starting from the determined start location. I think for now this would work fine, but there is at least the issue that would not work with fuzzy-matched completions.

What do you think, @kyliau and @andrius-pra?

@kyliau
Copy link
Contributor

kyliau commented Oct 25, 2019

I think it's a "good-enough" solution with the absolute span. In edge cases like you mentioned, we could just set the replacement span to the nearest word boundary.

mhevery pushed a commit to mhevery/angular that referenced this pull request Oct 25, 2019
…angular#33387)

Moves to using the absolute span of an expression AST (relative to an
entire template) rather than a relative span (relative to the start
of the expression) to find an expression AST given a position in a
template.

This is part of the changes needed to support text replacement in
templates (angular#33091).

PR Close angular#33387
@ayazhafiz
Copy link
Member Author

Okay, maybe it's even safer to implement expanding the word until a word boundary rather than relying on a span. Let me see how that does.

Adds a `replacementSpan` field on a completion that will allow typed
text to be replaced with the suggested completion value if a user
selects the completion. Previously, the completion value would simply be
appended to the text already typed. E.g. if we had

```
{{ti}}
```

typed in a template and `title` was recommended as a completion and
selected, the template would become

```
{{tititle}}
```

With `replacementSpan`, the original text `ti` will be replaced for
`title`.
packages/language-service/src/completions.ts Outdated Show resolved Hide resolved
packages/language-service/src/completions.ts Outdated Show resolved Hide resolved
packages/language-service/test/completions_spec.ts Outdated Show resolved Hide resolved
packages/language-service/src/completions.ts Show resolved Hide resolved
packages/language-service/src/completions.ts Show resolved Hide resolved
packages/language-service/test/completions_spec.ts Outdated Show resolved Hide resolved
@kyliau
Copy link
Contributor

kyliau commented Oct 28, 2019

Please also rebase this PR, thank you!

@ayazhafiz ayazhafiz requested a review from kyliau October 29, 2019 00:36
Copy link
Contributor

@kyliau kyliau left a comment

Choose a reason for hiding this comment

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

LGTM, thank you!

@ayazhafiz ayazhafiz added the action: merge The PR is ready for merge by the caretaker label Oct 29, 2019
matsko pushed a commit to matsko/angular that referenced this pull request Oct 30, 2019
Adds a `replacementSpan` field on a completion that will allow typed
text to be replaced with the suggested completion value if a user
selects the completion. Previously, the completion value would simply be
appended to the text already typed. E.g. if we had

```
{{ti}}
```

typed in a template and `title` was recommended as a completion and
selected, the template would become

```
{{tititle}}
```

With `replacementSpan`, the original text `ti` will be replaced for
`title`.

PR Close angular#33091
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
Adds a `replacementSpan` field on a completion that will allow typed
text to be replaced with the suggested completion value if a user
selects the completion. Previously, the completion value would simply be
appended to the text already typed. E.g. if we had

```
{{ti}}
```

typed in a template and `title` was recommended as a completion and
selected, the template would become

```
{{tititle}}
```

With `replacementSpan`, the original text `ti` will be replaced for
`title`.

PR Close angular#33091
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
Adds a `replacementSpan` field on a completion that will allow typed
text to be replaced with the suggested completion value if a user
selects the completion. Previously, the completion value would simply be
appended to the text already typed. E.g. if we had

```
{{ti}}
```

typed in a template and `title` was recommended as a completion and
selected, the template would become

```
{{tititle}}
```

With `replacementSpan`, the original text `ti` will be replaced for
`title`.

PR Close angular#33091
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Nov 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: language-service Issues related to Angular's VS Code language service cla: yes feature Issue that requests a new feature target: major This PR is targeted for the next major release workaround2: non-obvious
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants