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

Add support for inline completions (WIP) #2552

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion ColorSchemes/Breakers.sublime-color-scheme
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
{
"scope": "meta.semantic-token",
"background": "#00000001"
}
},
{
"scope": "meta.inline-completion",
"foreground": "color(var(grey3) alpha(0.6))",
"font_style": "italic"
},
]
}
7 changes: 6 additions & 1 deletion ColorSchemes/Celeste.sublime-color-scheme
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
{
"scope": "meta.semantic-token",
"background": "#00000001"
}
},
{
"scope": "meta.inline-completion",
"foreground": "color(var(black) alpha(0.6))",
"font_style": "italic"
},
]
}
7 changes: 6 additions & 1 deletion ColorSchemes/Mariana.sublime-color-scheme
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
{
"scope": "meta.semantic-token",
"background": "#00000001"
}
},
{
"scope": "meta.inline-completion",
"foreground": "color(var(white3) alpha(0.6))",
"font_style": "italic"
},
]
}
7 changes: 6 additions & 1 deletion ColorSchemes/Monokai.sublime-color-scheme
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
{
"scope": "meta.semantic-token",
"background": "#00000001"
}
},
{
"scope": "meta.inline-completion",
"foreground": "color(var(white3) alpha(0.6))",
"font_style": "italic"
},
]
}
7 changes: 6 additions & 1 deletion ColorSchemes/Sixteen.sublime-color-scheme
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
{
"scope": "meta.semantic-token",
"background": "#00000001"
}
},
{
"scope": "meta.inline-completion",
"foreground": "color(var(grey5) alpha(0.6))",
"font_style": "italic"
},
]
}
22 changes: 17 additions & 5 deletions Default.sublime-keymap
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@
// "args": {"overlay": "command_palette", "text": "LSP: "}
// },
// Insert/Replace Completions
// {
// "keys": ["UNBOUND"],
// "command": "lsp_commit_completion_with_opposite_insert_mode",
// "context": [
// {"key": "lsp.session_with_capability", "operand": "completionProvider"},
// {"key": "auto_complete_visible"}
// ]
// },
// Insert Inline Completion
{
"keys": ["alt+enter"],
Copy link
Member

Choose a reason for hiding this comment

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

What do you think of using tab for committing an inline completion?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm aware that Tab is used for it in VSCode and I think also in other IDEs like IntelliJ. But I doubt that it would be a good choice in Sublime; personally I use Tab to commit regular auto-complete ("auto_complete_commit_on_tab": true) and there are probably many users who do the same. I think we should use a key binding which is definitely not already in use by Sublime Text. However Alt + Enter is not the most ergonomic to use though; here I used it mostly for testing but we can discuss whether we find something better. Or maybe it should be left without any key binding by default and require users to set up an own key binding for it.

"command": "lsp_commit_completion_with_opposite_insert_mode",
"context": [
{"key": "lsp.session_with_capability", "operand": "completionProvider"},
{"key": "auto_complete_visible"}
]
"command": "lsp_commit_inline_completion",
"context": [{"key": "lsp.inline_completion_visible"}]
},
// Show next Inline Completion
// {
// "keys": ["UNBOUND"],
// "command": "lsp_next_inline_completion",
// "context": [{"key": "lsp.inline_completion_visible"}]
// },
// Save all open files that have a language server attached with lsp_save
// {
// "keys": ["UNBOUND"],
Expand Down
6 changes: 6 additions & 0 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
from .plugin.hover import LspToggleHoverPopupsCommand
from .plugin.inlay_hint import LspInlayHintClickCommand
from .plugin.inlay_hint import LspToggleInlayHintsCommand
from .plugin.inline_completion import LspCommitInlineCompletionCommand
from .plugin.inline_completion import LspInlineCompletionCommand
from .plugin.inline_completion import LspNextInlineCompletionCommand
from .plugin.panels import LspClearLogPanelCommand
from .plugin.panels import LspClearPanelCommand
from .plugin.panels import LspShowDiagnosticsPanelCommand
Expand Down Expand Up @@ -99,6 +102,7 @@
"LspCollapseTreeItemCommand",
"LspColorPresentationCommand",
"LspCommitCompletionWithOppositeInsertMode",
"LspCommitInlineCompletionCommand",
"LspCopyToClipboardFromBase64Command",
"LspDisableLanguageServerGloballyCommand",
"LspDisableLanguageServerInProjectCommand",
Expand All @@ -120,7 +124,9 @@
"LspHierarchyToggleCommand",
"LspHoverCommand",
"LspInlayHintClickCommand",
"LspInlineCompletionCommand",
"LspNextDiagnosticCommand",
"LspNextInlineCompletionCommand",
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure how to test the LspNextInlineCompletionCommand

Copy link
Member

Choose a reason for hiding this comment

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

Currently I trigger the autocomple,
that also triggers the inline completions request,
and I will see the inline completion phantom,
When I trigger the LspNextInlineCompletionCommand, I expect to see a different inline completion phantom, but I still see the same phantom. Do you see a different phantom when triggering the LspNextInlineCompletionCommand?

Copy link
Member Author

Choose a reason for hiding this comment

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

In my experiments, Tabby always provides only a single inline completion item. So it's expected that currently nothing happens when you trigger that command.

"LspOnDoubleClickCommand",
"LspOpenLinkCommand",
"LspOpenLocationCommand",
Expand Down
6 changes: 6 additions & 0 deletions docs/src/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,9 @@ The color scheme rule only works if the "background" color is (marginally) diffe
| ----- | ----------- |
| `markup.accent.codelens.lsp` | Accent color for code lens annotations |
| `markup.accent.codeaction.lsp` | Accent color for code action annotations |

### Inline Completions

| scope | description |
| ----- | ----------- |
| `meta.inline-completion.lsp` | Style for inline completions |
9 changes: 9 additions & 0 deletions docs/src/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ Inlay hints are disabled by default and can be enabled with the `"show_inlay_hin

!!! info "Some servers require additional settings to be enabled in order to show inlay hints."

## Inline Completions

Inline completions are typically provided by an AI code assistant.
They can span multiple lines and are rendered directly in the source code as grayed out text ("ghost text").

!!! note
Currently inline completions are only requested when you manually trigger auto-completions (<kbd>Ctrl</kbd> + <kbd>Space</kbd>).
Inline completions are disabled if you have enabled `"mini_auto_complete"`.

jwortmann marked this conversation as resolved.
Show resolved Hide resolved
## Server Commands

In Sublime Text you can bind any runnable command to a key or add it to various UI elements. Commands in Sublime Text are normally supplied by plugins or packages written in Python. A language server may provide a runnable command as well. These kinds of commands are wrapped in an `lsp_execute` Sublime command that you can bind to a key.
Expand Down
3 changes: 3 additions & 0 deletions docs/src/keyboard_shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
| Feature | Shortcut | Command |
| ------- | -------- | ------- |
| Auto Complete | <kbd>ctrl</kbd> <kbd>space</kbd> (also on macOS) | `auto_complete`
| Commit Inline Completion | <kbd>alt</kbd> <kbd>enter</kbd> | `lsp_commit_inline_completion`
| Expand Selection | unbound | `lsp_expand_selection`
| Find References | <kbd>shift</kbd> <kbd>f12</kbd> | `lsp_symbol_references`<br>Supports optional args: `{"include_declaration": true | false, "output_mode": "output_panel" | "quick_panel"}`.<br>Triggering from context menus while holding <kbd>ctrl</kbd> opens in "side by side" mode. Holding <kbd>shift</kbd> triggers opposite behavior relative to what `show_references_in_quick_panel` is set to.
| Fold | unbound | `lsp_fold`<br>Supports optional args: `{"strict": true/false}` - to configure whether to fold only when the caret is contained within the folded region (`true`), or even when it is anywhere on the starting line (`false`).
Expand All @@ -28,13 +29,15 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
| Next Diagnostic | unbound | `lsp_next_diagnostic`
| Previous Diagnostic | unbound | `lsp_prev_diagnostic`
| Rename | unbound | `lsp_symbol_rename`
| Request Inline Completions | unbound | `lsp_inline_completion`
| Restart Server | unbound | `lsp_restart_server`
| Run Code Action | unbound | `lsp_code_actions`
| Run Code Lens | unbound | `lsp_code_lens`
| Run Refactor Action | unbound | `lsp_code_actions`<br>With args: `{"only_kinds": ["refactor"]}`.
| Run Source Action | unbound | `lsp_code_actions`<br>With args: `{"only_kinds": ["source"]}`.
| Save All | unbound | `lsp_save_all`<br>Supports optional args `{"only_files": true | false}` - whether to ignore buffers which have no associated file on disk.
| Show Call Hierarchy | unbound | `lsp_call_hierarchy`
| Show next Inline Completion | unbound | `lsp_next_inline_completion`
| Show Type Hierarchy | unbound | `lsp_type_hierarchy`
| Signature Help | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>space</kbd> | `lsp_signature_help_show`
| Toggle Diagnostics Panel | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>m</kbd> | `lsp_show_diagnostics_panel`
Expand Down
73 changes: 73 additions & 0 deletions docs/src/language_servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,79 @@ If there are no setup steps for a language server on this page, but a [language
!!! info "For legacy ST3 docs, see [lsp.readthedocs.io](https://lsp.readthedocs.io)."


## Universal

### Tabby

[Tabby](https://tabby.tabbyml.com/) is a self-hosted AI coding assistant which can provide inline completions for [various programming languages](https://tabby.tabbyml.com/docs/references/programming-languages/).

In order to use Tabby you need a sufficiently fast GPU; the CPU version which can also be downloaded from the GitHub releases page is much too slow and it will result in timeouts for the completion requests.
Alternatively, Tabby can be setup on a separate server with capable hardware; see the [Configuration docs](https://tabby.tabbyml.com/docs/extensions/configurations/) for the required configuration details.
The following steps describe a local installation on a Windows PC with compatible Nvidia GPU. More installation methods and the steps for other operation systems are listed in the [Tabby docs](https://tabby.tabbyml.com/docs/quick-start/installation/docker/).

1. Download and install the CUDA Toolkit from <https://developer.nvidia.com/cuda-downloads>

2. Download and extract a CUDA version of Tabby from the [GitHub releases page](https://github.com/TabbyML/tabby/releases) (click on "Assets"); e.g. `tabby_x86_64-windows-msvc-cuda122.zip`

jwortmann marked this conversation as resolved.
Show resolved Hide resolved
3. On macOS and Linux it might be necessary to change the access permissions of `lama-server` and `tabby` to be executable:

```sh
$ tabby_aarch64-apple-darwin chmod +x llama-server
$ tabby_aarch64-apple-darwin chmod +x tabby
```

!!! note "On macOS you might get an error that “tabby” cannot be opened because it is from an unidentified developer."
After changing the permission to executable, right click on `tabby` and select "Open", that will get rid of the error.

4. Download a completion model (see <https://tabby.tabbyml.com/docs/models/> for available model files and GPU requirements):

```sh
tabby download --model StarCoder-1B
```

5. If necessary, edit the configuration file under `~/.tabby-client/agent/config.toml`, which is generated automatically on the first start of tabby-agent.
For example, to disable anonymous usage tracking add

```toml
[anonymousUsageTracking]
disable = true
```

6. Install the `tabby-agent` language server via npm (requires NodeJS):

```sh
npm install -g tabby-agent
```

7. Open `Preferences > Package Settings > LSP > Settings` and add the `"tabby"` client configuration to the `"clients"`:

```jsonc
{
"clients": {
"tabby": {
"enabled": true,
"command": ["tabby-agent", "--stdio"],
"selector": "source.js | source.python | source.rust", // replace with your relevant filetype(s)
"disabled_capabilities": {
"completionProvider": true
}
},
}
}
```

8. Manually start the Tabby backend:

```sh
tabby serve --model StarCoder-1B --no-webserver
```

The language server communicates with this backend, i.e. it needs to be running in order for `tabby-agent` to work.

9. Now you can open a file in Sublime Text and start coding.
Inline completions are requested when you manually trigger auto-complete via <kbd>Ctrl</kbd> + <kbd>Space</kbd>.


## Angular

Follow installation instructions on [LSP-angular](https://github.com/sublimelsp/LSP-angular).
Expand Down
10 changes: 10 additions & 0 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
from enum import IntEnum, IntFlag
from typing import Any, Callable, Generator, List, Protocol, TypeVar
from typing import cast
from typing import TYPE_CHECKING
from typing_extensions import TypeAlias, TypeGuard
from weakref import WeakSet
import functools
Expand All @@ -122,6 +123,11 @@
import sublime
import weakref


if TYPE_CHECKING:
from ..inline_completion import InlineCompletionData


InitCallback: TypeAlias = Callable[['Session', bool], None]
T = TypeVar('T')

Expand Down Expand Up @@ -325,6 +331,9 @@ def get_initialize_params(variables: dict[str, str], workspace_folders: list[Wor
"itemDefaults": ["editRange", "insertTextFormat", "data"]
}
},
"inlineCompletion": {
"dynamicRegistration": True
},
"signatureHelp": {
"dynamicRegistration": True,
"contextSupport": True,
Expand Down Expand Up @@ -701,6 +710,7 @@ class AbstractViewListener(metaclass=ABCMeta):

view = cast(sublime.View, None)
hover_provider_count = 0
inline_completion = cast('InlineCompletionData', None)

@abstractmethod
def session_async(self, capability: str, point: int | None = None) -> Session | None:
Expand Down
8 changes: 8 additions & 0 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from .core.windows import WindowManager
from .folding_range import folding_range_to_range
from .hover import code_actions_content
from .inline_completion import InlineCompletionData
from .session_buffer import SessionBuffer
from .session_view import SessionView
from functools import partial
Expand Down Expand Up @@ -197,6 +198,7 @@ def on_change() -> None:
self._stored_selection: list[sublime.Region] = []
self._should_format_on_paste = False
self.hover_provider_count = 0
self.inline_completion = InlineCompletionData(self.view, 'lsp_inline_completion')
self._setup()

def __del__(self) -> None:
Expand Down Expand Up @@ -226,6 +228,7 @@ def _cleanup(self) -> None:
self._stored_selection = []
self.view.erase_status(AbstractViewListener.TOTAL_ERRORS_AND_WARNINGS_STATUS_KEY)
self._clear_highlight_regions()
self.inline_completion.clear_async()
self._clear_session_views_async()

def _reset(self) -> None:
Expand Down Expand Up @@ -418,6 +421,7 @@ def on_selection_modified_async(self) -> None:
if not self._is_in_higlighted_region(first_region.b):
self._clear_highlight_regions()
self._clear_code_actions_annotation()
self.inline_completion.clear_async()
if userprefs().document_highlight_style or userprefs().show_code_actions:
self._when_selection_remains_stable_async(
self._on_selection_modified_debounced_async, first_region, after_ms=self.debounce_time)
Expand Down Expand Up @@ -511,6 +515,8 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo
if not session_view:
return not operand
return operand == bool(session_view.session_buffer.get_document_link_at_point(self.view, position))
elif key == 'lsp.inline_completion_visible' and operator == sublime.QueryOperator.EQUAL:
return operand == self.inline_completion.visible
return None

@requires_session
Expand Down Expand Up @@ -560,6 +566,7 @@ def _on_hover_gutter_async(self, point: int) -> None:
def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, dict] | None:
if command_name == "auto_complete":
self._auto_complete_triggered_manually = True
self.view.run_command('lsp_inline_completion')
elif command_name == "show_scope_name" and userprefs().semantic_highlighting:
session = self.session_async("semanticTokensProvider")
if session:
Expand Down Expand Up @@ -990,6 +997,7 @@ def _on_view_updated_async(self) -> None:
if first_region is None:
return
self._clear_highlight_regions()
self.inline_completion.clear_async()
if userprefs().document_highlight_style:
self._when_selection_remains_stable_async(
self._do_highlights_async, first_region, after_ms=self.debounce_time)
Expand Down
Loading