Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
166dc56
feat(lsp): add textDocument/inlineCompletion support
devmanuelli Nov 26, 2025
6ebd915
fix(lsp): handle inline completion range for correct ghost text display
devmanuelli Nov 26, 2025
d45fae1
fix(lsp): render ghost text without shifting cursor position
devmanuelli Nov 26, 2025
5b7d6c1
feat(lsp): support multiple inline completions with cycling
devmanuelli Nov 27, 2025
6af9ca2
refactor(lsp): simplify InlineCompletion and fix non-ASCII handling
devmanuelli Nov 28, 2025
68e2de0
fix(lsp): add document version check for inline completion staleness
devmanuelli Nov 28, 2025
82c75cb
feat(lsp): add manual trigger and auto-trigger config for inline comp…
devmanuelli Nov 28, 2025
ff17249
feat(lsp): store all inline completion items from LSP response
devmanuelli Nov 28, 2025
f817a78
fix(ui): expand tabs in ghost text using document tab width
devmanuelli Nov 30, 2025
cfe6167
refactor(ui): integrate ghost text with annotation system
devmanuelli Nov 30, 2025
bdb3615
fix(ui): render EOL ghost text on cursor position with cursor style
devmanuelli Dec 1, 2025
2f70f8d
fix(ui): use overlays for mid-line ghost text to prevent cursor shift
devmanuelli Dec 1, 2025
0b04aaa
fix(ui): dismiss inline completion on cursor movement
devmanuelli Dec 1, 2025
bd4fd38
fix(ui): improve multi-line ghost text rendering mid-line
devmanuelli Dec 6, 2025
395df7d
fix(ui): avoid duplicate rest-of-line in multi-line ghost text
devmanuelli Dec 6, 2025
e927a22
fix(ui): pad multi-line ghost text to cover entire rest-of-line
devmanuelli Dec 9, 2025
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
5 changes: 5 additions & 0 deletions book/src/generated/static-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@
| `keep_primary_selection` | Keep primary selection | normal: `` , ``, select: `` , `` |
| `remove_primary_selection` | Remove primary selection | normal: `` <A-,> ``, select: `` <A-,> `` |
| `completion` | Invoke completion popup | insert: `` <C-x> `` |
| `inline_completion_accept` | Accept inline completion | |
| `inline_completion_dismiss` | Dismiss inline completion | |
| `inline_completion_next` | Cycle to next inline completion | |
| `inline_completion_prev` | Cycle to previous inline completion | |
| `inline_completion_trigger` | Trigger inline completion | |
| `hover` | Show docs for item under cursor | normal: `` <space>k ``, select: `` <space>k `` |
| `toggle_comments` | Comment/uncomment selections | normal: `` <C-c> ``, `` <space>c ``, select: `` <C-c> ``, `` <space>c `` |
| `toggle_line_comments` | Line comment/uncomment selections | normal: `` <space><A-c> ``, select: `` <space><A-c> `` |
Expand Down
2 changes: 2 additions & 0 deletions helix-core/src/syntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ pub enum LanguageServerFeature {
RenameSymbol,
InlayHints,
DocumentColors,
InlineCompletion,
}

impl Display for LanguageServerFeature {
Expand All @@ -299,6 +300,7 @@ impl Display for LanguageServerFeature {
RenameSymbol => "rename-symbol",
InlayHints => "inlay-hints",
DocumentColors => "document-colors",
InlineCompletion => "inline-completion",
};
write!(f, "{feature}",)
}
Expand Down
4 changes: 2 additions & 2 deletions helix-lsp-types/src/inline_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub struct InlineCompletionParams {
pub context: InlineCompletionContext,
}

/// Describes how an [`InlineCompletionItemProvider`] was triggered.
/// Describes how an `InlineCompletionItemProvider` was triggered.
///
/// @since 3.18.0
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
Expand Down Expand Up @@ -137,7 +137,7 @@ pub struct InlineCompletionItem {
/// Is used both for the preview and the accept operation.
pub insert_text: String,
/// A text that is used to decide if this inline completion should be
/// shown. When `falsy` the [`InlineCompletionItem::insertText`] is
/// shown. When `falsy` the [`InlineCompletionItem::insert_text`] is
/// used.
///
/// An inline completion is shown if the text to replace is a prefix of the
Expand Down
4 changes: 0 additions & 4 deletions helix-lsp-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ pub use inlay_hint::*;
mod inline_value;
pub use inline_value::*;

#[cfg(feature = "proposed")]
mod inline_completion;
#[cfg(feature = "proposed")]
pub use inline_completion::*;

mod moniker;
Expand Down Expand Up @@ -1596,7 +1594,6 @@ pub struct TextDocumentClientCapabilities {
///
/// @since 3.18.0
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(feature = "proposed")]
pub inline_completion: Option<InlineCompletionClientCapabilities>,
}

Expand Down Expand Up @@ -2060,7 +2057,6 @@ pub struct ServerCapabilities {
///
/// @since 3.18.0
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(feature = "proposed")]
pub inline_completion_provider: Option<OneOf<bool, InlineCompletionOptions>>,

/// Experimental server capabilities.
Expand Down
2 changes: 0 additions & 2 deletions helix-lsp-types/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,8 @@ impl Request for PrepareRenameRequest {
}

#[derive(Debug)]
#[cfg(feature = "proposed")]
pub enum InlineCompletionRequest {}

#[cfg(feature = "proposed")]
impl Request for InlineCompletionRequest {
type Params = InlineCompletionParams;
type Result = Option<InlineCompletionResponse>;
Expand Down
30 changes: 30 additions & 0 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ impl Client {
| ColorProviderCapability::Options(_)
)
),
LanguageServerFeature::InlineCompletion => matches!(
capabilities.inline_completion_provider,
Some(OneOf::Left(true) | OneOf::Right(_))
),
}
}

Expand Down Expand Up @@ -701,6 +705,9 @@ impl Client {
dynamic_registration: Some(false),
resolve_support: None,
}),
inline_completion: Some(lsp::InlineCompletionClientCapabilities {
dynamic_registration: Some(false),
}),
..Default::default()
}),
window: Some(lsp::WindowClientCapabilities {
Expand Down Expand Up @@ -1135,6 +1142,29 @@ impl Client {
Some(self.call::<lsp::request::InlayHintRequest>(params))
}

pub fn inline_completion(
&self,
text_document: lsp::TextDocumentIdentifier,
position: lsp::Position,
context: lsp::InlineCompletionContext,
work_done_token: Option<lsp::ProgressToken>,
) -> Option<impl Future<Output = Result<Option<lsp::InlineCompletionResponse>>>> {
if !self.supports_feature(LanguageServerFeature::InlineCompletion) {
return None;
}

let params = lsp::InlineCompletionParams {
text_document_position: lsp::TextDocumentPositionParams {
text_document,
position,
},
context,
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
};

Some(self.call::<lsp::request::InlineCompletionRequest>(params))
}

pub fn text_document_document_color(
&self,
text_document: lsp::TextDocumentIdentifier,
Expand Down
53 changes: 53 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,11 @@ impl MappableCommand {
keep_primary_selection, "Keep primary selection",
remove_primary_selection, "Remove primary selection",
completion, "Invoke completion popup",
inline_completion_accept, "Accept inline completion",
inline_completion_dismiss, "Dismiss inline completion",
inline_completion_next, "Cycle to next inline completion",
inline_completion_prev, "Cycle to previous inline completion",
inline_completion_trigger, "Trigger inline completion",
hover, "Show docs for item under cursor",
toggle_comments, "Comment/uncomment selections",
toggle_line_comments, "Line comment/uncomment selections",
Expand Down Expand Up @@ -5262,6 +5267,54 @@ pub fn completion(cx: &mut Context) {
.trigger_completions(cursor, doc.id(), view.id);
}

pub fn inline_completion_accept(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
if let Some(c) = doc.inline_completions.take_and_clear() {
let text = doc.text();
let cursor = doc.selection(view.id).primary().cursor(text.slice(..));
let new_cursor = cursor + c.ghost_text.chars().count();
let t = Transaction::change(
text,
std::iter::once((cursor, c.replace_range.to(), Some(c.ghost_text.into()))),
)
.with_selection(Selection::point(new_cursor));
doc.apply(&t, view.id);
}
}

pub fn inline_completion_dismiss(cx: &mut Context) {
if doc_mut!(cx.editor)
.inline_completions
.take_and_clear()
.is_none()
{
normal_mode(cx);
}
}

pub fn inline_completion_next(cx: &mut Context) {
use helix_core::movement::Direction;
let doc = doc_mut!(cx.editor);
doc.inline_completions.cycle(Direction::Forward);
doc.inline_completions
.rebuild_overlays(&mut doc.inline_completion_overlays);
}

pub fn inline_completion_prev(cx: &mut Context) {
use helix_core::movement::Direction;
let doc = doc_mut!(cx.editor);
doc.inline_completions.cycle(Direction::Backward);
doc.inline_completions
.rebuild_overlays(&mut doc.inline_completion_overlays);
}

pub fn inline_completion_trigger(_cx: &mut Context) {
use helix_lsp::lsp;
crate::handlers::inline_completion::trigger_inline_completion(
lsp::InlineCompletionTriggerKind::Invoked,
);
}

// comments
type CommentTransactionFn = fn(
line_token: Option<&str>,
Expand Down
7 changes: 6 additions & 1 deletion helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,28 @@ use crate::handlers::signature_help::SignatureHelpHandler;
pub use helix_view::handlers::{word_index, Handlers};

use self::document_colors::DocumentColorsHandler;
use self::inline_completion::InlineCompletionHandler;

mod auto_save;
pub mod completion;
pub mod diagnostics;
mod document_colors;
pub mod inline_completion;
mod prompt;
mod signature_help;
mod snippet;

pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
events::register();

let event_tx = completion::CompletionHandler::new(config).spawn();
let event_tx = completion::CompletionHandler::new(config.clone()).spawn();
let signature_hints = SignatureHelpHandler::new().spawn();
let auto_save = AutoSaveHandler::new().spawn();
let document_colors = DocumentColorsHandler::default().spawn();
let word_index = word_index::Handler::spawn();
let pull_diagnostics = PullDiagnosticsHandler::default().spawn();
let pull_all_documents_diagnostics = PullAllDocumentsDiagnosticHandler::default().spawn();
let inline_completions = InlineCompletionHandler::new(config.clone()).spawn();

let handlers = Handlers {
completions: helix_view::handlers::completion::CompletionHandler::new(event_tx),
Expand All @@ -41,6 +44,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
word_index,
pull_diagnostics,
pull_all_documents_diagnostics,
inline_completions,
};

helix_view::handlers::register_hooks(&handlers);
Expand All @@ -50,6 +54,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
diagnostics::register_hooks(&handlers);
snippet::register_hooks(&handlers);
document_colors::register_hooks(&handlers);
inline_completion::register_hooks(&handlers);
prompt::register_hooks(&handlers);
handlers
}
Loading
Loading