@@ -2122,6 +2122,13 @@ impl Editor {
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
+ if text.len() == 1 {
+ let input_char = text.chars().next().expect("single char input");
+ if let Some(on_type_format_task) = this.trigger_on_type_format(input_char, cx) {
+ on_type_format_task.detach_and_log_err(cx);
+ }
+ }
+
if had_active_copilot_suggestion {
this.refresh_copilot_suggestions(true, cx);
if !this.has_active_copilot_suggestion(cx) {
@@ -2500,6 +2507,23 @@ impl Editor {
}
}
+ fn trigger_on_type_format(
+ &self,
+ input: char,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Task<Result<()>>> {
+ let project = self.project.as_ref()?;
+ let position = self.selections.newest_anchor().head();
+ let (buffer, buffer_position) = self
+ .buffer
+ .read(cx)
+ .text_anchor_for_position(position.clone(), cx)?;
+
+ Some(project.update(cx, |project, cx| {
+ project.on_type_format(buffer.clone(), buffer_position, input, cx)
+ }))
+ }
+
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
if self.pending_rename.is_some() {
return;
@@ -2,7 +2,7 @@ use crate::{
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project,
ProjectTransaction,
};
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use client::proto::{self, PeerId};
use fs::LineEnding;
@@ -109,6 +109,12 @@ pub(crate) struct GetCodeActions {
pub range: Range<Anchor>,
}
+pub(crate) struct OnTypeFormatting {
+ pub position: PointUtf16,
+ pub new_char: char,
+ // TODO kb formatting options?
+}
+
#[async_trait(?Send)]
impl LspCommand for PrepareRename {
type Response = Option<Range<Anchor>>;
@@ -1596,3 +1602,98 @@ impl LspCommand for GetCodeActions {
message.buffer_id
}
}
+
+#[async_trait(?Send)]
+impl LspCommand for OnTypeFormatting {
+ type Response = Vec<(Range<Anchor>, String)>;
+ type LspRequest = lsp::request::OnTypeFormatting;
+ type ProtoRequest = proto::PerformRename;
+
+ fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
+ let Some(on_type_formatting_options) = &server_capabilities.document_on_type_formatting_provider else { return false };
+ on_type_formatting_options
+ .first_trigger_character
+ .contains(self.new_char)
+ || on_type_formatting_options
+ .more_trigger_character
+ .iter()
+ .flatten()
+ .any(|chars| chars.contains(self.new_char))
+ }
+
+ fn to_lsp(
+ &self,
+ path: &Path,
+ _: &Buffer,
+ _: &Arc<LanguageServer>,
+ _: &AppContext,
+ ) -> lsp::DocumentOnTypeFormattingParams {
+ lsp::DocumentOnTypeFormattingParams {
+ text_document_position: lsp::TextDocumentPositionParams::new(
+ lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
+ point_to_lsp(self.position),
+ ),
+ ch: self.new_char.to_string(),
+ // TODO kb pass current editor ones
+ options: lsp::FormattingOptions::default(),
+ }
+ }
+
+ async fn response_from_lsp(
+ self,
+ message: Option<Vec<lsp::TextEdit>>,
+ project: ModelHandle<Project>,
+ buffer: ModelHandle<Buffer>,
+ server_id: LanguageServerId,
+ mut cx: AsyncAppContext,
+ ) -> Result<Vec<(Range<Anchor>, String)>> {
+ cx.update(|cx| {
+ project.update(cx, |project, cx| {
+ project.edits_from_lsp(&buffer, message.into_iter().flatten(), server_id, None, cx)
+ })
+ })
+ .await
+ .context("LSP edits conversion")
+ }
+
+ fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
+ todo!("TODO kb")
+ }
+
+ async fn from_proto(
+ message: proto::PerformRename,
+ _: ModelHandle<Project>,
+ buffer: ModelHandle<Buffer>,
+ mut cx: AsyncAppContext,
+ ) -> Result<Self> {
+ todo!("TODO kb")
+ }
+
+ fn response_to_proto(
+ response: Vec<(Range<Anchor>, String)>,
+ project: &mut Project,
+ peer_id: PeerId,
+ _: &clock::Global,
+ cx: &mut AppContext,
+ ) -> proto::PerformRenameResponse {
+ // let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
+ // proto::PerformRenameResponse {
+ // transaction: Some(transaction),
+ // }
+ todo!("TODO kb")
+ }
+
+ async fn response_from_proto(
+ self,
+ message: proto::PerformRenameResponse,
+ project: ModelHandle<Project>,
+ _: ModelHandle<Buffer>,
+ mut cx: AsyncAppContext,
+ ) -> Result<Vec<(Range<Anchor>, String)>> {
+ todo!("TODO kb")
+ }
+
+ fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
+ message.buffer_id
+ }
+}
@@ -4209,6 +4209,40 @@ impl Project {
)
}
+ pub fn on_type_format<T: ToPointUtf16>(
+ &self,
+ buffer: ModelHandle<Buffer>,
+ position: T,
+ input: char,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<()>> {
+ let position = position.to_point_utf16(buffer.read(cx));
+ let edits_task = self.request_lsp(
+ buffer.clone(),
+ OnTypeFormatting {
+ position,
+ new_char: input,
+ },
+ cx,
+ );
+
+ cx.spawn(|_project, mut cx| async move {
+ let edits = edits_task
+ .await
+ .context("requesting OnTypeFormatting edits for char '{new_char}'")?;
+
+ if !edits.is_empty() {
+ cx.update(|cx| {
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
+ });
+ }
+
+ Ok(())
+ })
+ }
+
#[allow(clippy::type_complexity)]
pub fn search(
&self,
@@ -6349,7 +6383,7 @@ impl Project {
}
#[allow(clippy::type_complexity)]
- fn edits_from_lsp(
+ pub fn edits_from_lsp(
&mut self,
buffer: &ModelHandle<Buffer>,
lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,