Move formatting to LSP store (#18242)
Release Notes: - ssh-remoting: Fixed format on save --------- Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
e95e1c9ae5
commit
e4080ef565
8 changed files with 655 additions and 657 deletions
|
@ -280,7 +280,7 @@ impl ActivityIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show any formatting failure
|
// Show any formatting failure
|
||||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
if let Some(failure) = self.project.read(cx).last_formatting_failure(cx) {
|
||||||
return Some(Content {
|
return Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
Icon::new(IconName::Warning)
|
Icon::new(IconName::Warning)
|
||||||
|
|
|
@ -28,8 +28,8 @@ use live_kit_client::MacOSDisplay;
|
||||||
use lsp::LanguageServerId;
|
use lsp::LanguageServerId;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{
|
use project::{
|
||||||
search::SearchQuery, search::SearchResult, DiagnosticSummary, FormatTrigger, HoverBlockKind,
|
lsp_store::FormatTrigger, search::SearchQuery, search::SearchResult, DiagnosticSummary,
|
||||||
Project, ProjectPath,
|
HoverBlockKind, Project, ProjectPath,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
|
@ -122,8 +122,8 @@ use ordered_float::OrderedFloat;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||||
use project::{
|
use project::{
|
||||||
CodeAction, Completion, CompletionIntent, FormatTrigger, Item, Location, Project, ProjectPath,
|
lsp_store::FormatTrigger, CodeAction, Completion, CompletionIntent, Item, Location, Project,
|
||||||
ProjectTransaction, TaskSourceKind,
|
ProjectPath, ProjectTransaction, TaskSourceKind,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::{proto::*, ErrorExt};
|
use rpc::{proto::*, ErrorExt};
|
||||||
|
|
|
@ -20,8 +20,8 @@ use language::{
|
||||||
};
|
};
|
||||||
use multi_buffer::AnchorRangeExt;
|
use multi_buffer::AnchorRangeExt;
|
||||||
use project::{
|
use project::{
|
||||||
project_settings::ProjectSettings, search::SearchQuery, FormatTrigger, Item as _, Project,
|
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Item as _,
|
||||||
ProjectPath,
|
Project, ProjectPath,
|
||||||
};
|
};
|
||||||
use rpc::proto::{self, update_view, PeerId};
|
use rpc::proto::{self, update_view, PeerId};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer_store::{BufferStore, BufferStoreEvent},
|
buffer_store::{BufferStore, BufferStoreEvent},
|
||||||
|
deserialize_code_actions,
|
||||||
environment::ProjectEnvironment,
|
environment::ProjectEnvironment,
|
||||||
lsp_command::{self, *},
|
lsp_command::{self, *},
|
||||||
lsp_ext_command,
|
lsp_ext_command,
|
||||||
|
@ -19,7 +20,7 @@ use futures::{
|
||||||
future::{join_all, BoxFuture, Shared},
|
future::{join_all, BoxFuture, Shared},
|
||||||
select,
|
select,
|
||||||
stream::FuturesUnordered,
|
stream::FuturesUnordered,
|
||||||
Future, FutureExt, StreamExt,
|
AsyncWriteExt, Future, FutureExt, StreamExt,
|
||||||
};
|
};
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -29,12 +30,13 @@ use gpui::{
|
||||||
use http_client::{AsyncBody, HttpClient, Request, Response, Uri};
|
use http_client::{AsyncBody, HttpClient, Request, Response, Uri};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{
|
language_settings::{
|
||||||
all_language_settings, language_settings, AllLanguageSettings, LanguageSettings,
|
all_language_settings, language_settings, AllLanguageSettings, FormatOnSave, Formatter,
|
||||||
|
LanguageSettings, SelectedFormatter,
|
||||||
},
|
},
|
||||||
markdown, point_to_lsp, prepare_completion_documentation,
|
markdown, point_to_lsp, prepare_completion_documentation,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
||||||
DiagnosticEntry, DiagnosticSet, Documentation, File as _, Language, LanguageConfig,
|
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
|
||||||
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter,
|
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter,
|
||||||
LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
|
LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
|
||||||
ToPointUtf16, Transaction, Unclipped,
|
ToPointUtf16, Transaction, Unclipped,
|
||||||
|
@ -90,12 +92,38 @@ const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||||
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
|
pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum FormatTrigger {
|
||||||
|
Save,
|
||||||
|
Manual,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently, formatting operations are represented differently depending on
|
||||||
|
// whether they come from a language server or an external command.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FormatOperation {
|
||||||
|
Lsp(Vec<(Range<Anchor>, String)>),
|
||||||
|
External(Diff),
|
||||||
|
Prettier(Diff),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatTrigger {
|
||||||
|
fn from_proto(value: i32) -> FormatTrigger {
|
||||||
|
match value {
|
||||||
|
0 => FormatTrigger::Save,
|
||||||
|
1 => FormatTrigger::Manual,
|
||||||
|
_ => FormatTrigger::Save,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LocalLspStore {
|
pub struct LocalLspStore {
|
||||||
http_client: Option<Arc<dyn HttpClient>>,
|
http_client: Option<Arc<dyn HttpClient>>,
|
||||||
environment: Model<ProjectEnvironment>,
|
environment: Model<ProjectEnvironment>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
yarn: Model<YarnPathStore>,
|
yarn: Model<YarnPathStore>,
|
||||||
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
|
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
|
||||||
|
buffers_being_formatted: HashSet<BufferId>,
|
||||||
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
|
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
|
||||||
language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
|
language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
|
||||||
language_server_watcher_registrations:
|
language_server_watcher_registrations:
|
||||||
|
@ -104,6 +132,7 @@ pub struct LocalLspStore {
|
||||||
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
|
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
|
||||||
prettier_store: Model<PrettierStore>,
|
prettier_store: Model<PrettierStore>,
|
||||||
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
|
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
|
||||||
|
last_formatting_failure: Option<String>,
|
||||||
_subscription: gpui::Subscription,
|
_subscription: gpui::Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +157,485 @@ impl LocalLspStore {
|
||||||
futures::future::join_all(shutdown_futures).await;
|
futures::future::join_all(shutdown_futures).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async fn format_locally(
|
||||||
|
lsp_store: WeakModel<LspStore>,
|
||||||
|
mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
|
||||||
|
push_to_history: bool,
|
||||||
|
trigger: FormatTrigger,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<ProjectTransaction> {
|
||||||
|
// Do not allow multiple concurrent formatting requests for the
|
||||||
|
// same buffer.
|
||||||
|
lsp_store.update(&mut cx, |this, cx| {
|
||||||
|
let this = this.as_local_mut().unwrap();
|
||||||
|
buffers_with_paths.retain(|(buffer, _)| {
|
||||||
|
this.buffers_being_formatted
|
||||||
|
.insert(buffer.read(cx).remote_id())
|
||||||
|
});
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let _cleanup = defer({
|
||||||
|
let this = lsp_store.clone();
|
||||||
|
let mut cx = cx.clone();
|
||||||
|
let buffers = &buffers_with_paths;
|
||||||
|
move || {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let this = this.as_local_mut().unwrap();
|
||||||
|
for (buffer, _) in buffers {
|
||||||
|
this.buffers_being_formatted
|
||||||
|
.remove(&buffer.read(cx).remote_id());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
|
for (buffer, buffer_abs_path) in &buffers_with_paths {
|
||||||
|
let (primary_adapter_and_server, adapters_and_servers) =
|
||||||
|
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
|
||||||
|
let adapters_and_servers = lsp_store
|
||||||
|
.language_servers_for_buffer(buffer, cx)
|
||||||
|
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let primary_adapter = lsp_store
|
||||||
|
.primary_language_server_for_buffer(buffer, cx)
|
||||||
|
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()));
|
||||||
|
|
||||||
|
(primary_adapter, adapters_and_servers)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let settings = buffer.update(&mut cx, |buffer, cx| {
|
||||||
|
language_settings(buffer.language(), buffer.file(), cx).clone()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
|
||||||
|
let ensure_final_newline = settings.ensure_final_newline_on_save;
|
||||||
|
|
||||||
|
// First, format buffer's whitespace according to the settings.
|
||||||
|
let trailing_whitespace_diff = if remove_trailing_whitespace {
|
||||||
|
Some(
|
||||||
|
buffer
|
||||||
|
.update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
|
||||||
|
buffer.finalize_last_transaction();
|
||||||
|
buffer.start_transaction();
|
||||||
|
if let Some(diff) = trailing_whitespace_diff {
|
||||||
|
buffer.apply_diff(diff, cx);
|
||||||
|
}
|
||||||
|
if ensure_final_newline {
|
||||||
|
buffer.ensure_final_newline(cx);
|
||||||
|
}
|
||||||
|
buffer.end_transaction(cx)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Apply the `code_actions_on_format` before we run the formatter.
|
||||||
|
let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
|
||||||
|
#[allow(clippy::nonminimal_bool)]
|
||||||
|
if !code_actions.is_empty()
|
||||||
|
&& !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
|
||||||
|
{
|
||||||
|
LspStore::execute_code_actions_on_servers(
|
||||||
|
&lsp_store,
|
||||||
|
&adapters_and_servers,
|
||||||
|
code_actions,
|
||||||
|
buffer,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply language-specific formatting using either the primary language server
|
||||||
|
// or external command.
|
||||||
|
// Except for code actions, which are applied with all connected language servers.
|
||||||
|
let primary_language_server =
|
||||||
|
primary_adapter_and_server.map(|(_adapter, server)| server.clone());
|
||||||
|
let server_and_buffer = primary_language_server
|
||||||
|
.as_ref()
|
||||||
|
.zip(buffer_abs_path.as_ref());
|
||||||
|
|
||||||
|
let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
|
||||||
|
language_settings(buffer.language(), buffer.file(), cx)
|
||||||
|
.prettier
|
||||||
|
.clone()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut format_operations: Vec<FormatOperation> = vec![];
|
||||||
|
{
|
||||||
|
match trigger {
|
||||||
|
FormatTrigger::Save => {
|
||||||
|
match &settings.format_on_save {
|
||||||
|
FormatOnSave::Off => {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
FormatOnSave::On => {
|
||||||
|
match &settings.formatter {
|
||||||
|
SelectedFormatter::Auto => {
|
||||||
|
// do the auto-format: prefer prettier, fallback to primary language server
|
||||||
|
let diff = {
|
||||||
|
if prettier_settings.allowed {
|
||||||
|
Self::perform_format(
|
||||||
|
&Formatter::Prettier,
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Self::perform_format(
|
||||||
|
&Formatter::LanguageServer { name: None },
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
if let Some(op) = diff {
|
||||||
|
format_operations.push(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectedFormatter::List(formatters) => {
|
||||||
|
for formatter in formatters.as_ref() {
|
||||||
|
let diff = Self::perform_format(
|
||||||
|
formatter,
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
if let Some(op) = diff {
|
||||||
|
format_operations.push(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
// format with formatter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormatOnSave::List(formatters) => {
|
||||||
|
for formatter in formatters.as_ref() {
|
||||||
|
let diff = Self::perform_format(
|
||||||
|
formatter,
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
if let Some(op) = diff {
|
||||||
|
format_operations.push(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormatTrigger::Manual => {
|
||||||
|
match &settings.formatter {
|
||||||
|
SelectedFormatter::Auto => {
|
||||||
|
// do the auto-format: prefer prettier, fallback to primary language server
|
||||||
|
let diff = {
|
||||||
|
if prettier_settings.allowed {
|
||||||
|
Self::perform_format(
|
||||||
|
&Formatter::Prettier,
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Self::perform_format(
|
||||||
|
&Formatter::LanguageServer { name: None },
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if let Some(op) = diff {
|
||||||
|
format_operations.push(op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectedFormatter::List(formatters) => {
|
||||||
|
for formatter in formatters.as_ref() {
|
||||||
|
// format with formatter
|
||||||
|
let diff = Self::perform_format(
|
||||||
|
formatter,
|
||||||
|
server_and_buffer,
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
&settings,
|
||||||
|
&adapters_and_servers,
|
||||||
|
push_to_history,
|
||||||
|
&mut project_transaction,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
if let Some(op) = diff {
|
||||||
|
format_operations.push(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.update(&mut cx, |b, cx| {
|
||||||
|
// If the buffer had its whitespace formatted and was edited while the language-specific
|
||||||
|
// formatting was being computed, avoid applying the language-specific formatting, because
|
||||||
|
// it can't be grouped with the whitespace formatting in the undo history.
|
||||||
|
if let Some(transaction_id) = whitespace_transaction_id {
|
||||||
|
if b.peek_undo_stack()
|
||||||
|
.map_or(true, |e| e.transaction_id() != transaction_id)
|
||||||
|
{
|
||||||
|
format_operations.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply any language-specific formatting, and group the two formatting operations
|
||||||
|
// in the buffer's undo history.
|
||||||
|
for operation in format_operations {
|
||||||
|
match operation {
|
||||||
|
FormatOperation::Lsp(edits) => {
|
||||||
|
b.edit(edits, None, cx);
|
||||||
|
}
|
||||||
|
FormatOperation::External(diff) => {
|
||||||
|
b.apply_diff(diff, cx);
|
||||||
|
}
|
||||||
|
FormatOperation::Prettier(diff) => {
|
||||||
|
b.apply_diff(diff, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(transaction_id) = whitespace_transaction_id {
|
||||||
|
b.group_until_transaction(transaction_id);
|
||||||
|
} else if let Some(transaction) = project_transaction.0.get(buffer) {
|
||||||
|
b.group_until_transaction(transaction.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(transaction) = b.finalize_last_transaction().cloned() {
|
||||||
|
if !push_to_history {
|
||||||
|
b.forget_transaction(transaction.id);
|
||||||
|
}
|
||||||
|
project_transaction.0.insert(buffer.clone(), transaction);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(project_transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
async fn perform_format(
|
||||||
|
formatter: &Formatter,
|
||||||
|
primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
|
||||||
|
lsp_store: WeakModel<LspStore>,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
buffer_abs_path: &Option<PathBuf>,
|
||||||
|
settings: &LanguageSettings,
|
||||||
|
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
|
||||||
|
push_to_history: bool,
|
||||||
|
transaction: &mut ProjectTransaction,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Result<Option<FormatOperation>, anyhow::Error> {
|
||||||
|
let result = match formatter {
|
||||||
|
Formatter::LanguageServer { name } => {
|
||||||
|
if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
|
||||||
|
let language_server = if let Some(name) = name {
|
||||||
|
adapters_and_servers
|
||||||
|
.iter()
|
||||||
|
.find_map(|(adapter, server)| {
|
||||||
|
adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
|
||||||
|
})
|
||||||
|
.unwrap_or(language_server)
|
||||||
|
} else {
|
||||||
|
language_server
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(FormatOperation::Lsp(
|
||||||
|
LspStore::format_via_lsp(
|
||||||
|
&lsp_store,
|
||||||
|
buffer,
|
||||||
|
buffer_abs_path,
|
||||||
|
language_server,
|
||||||
|
settings,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("failed to format via language server")?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Formatter::Prettier => {
|
||||||
|
let prettier = lsp_store.update(cx, |lsp_store, _cx| {
|
||||||
|
lsp_store.prettier_store().unwrap().downgrade()
|
||||||
|
})?;
|
||||||
|
prettier_store::format_with_prettier(&prettier, buffer, cx)
|
||||||
|
.await
|
||||||
|
.transpose()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
Formatter::External { command, arguments } => {
|
||||||
|
let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
|
||||||
|
Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)
|
||||||
|
.await
|
||||||
|
.context(format!(
|
||||||
|
"failed to format via external command {:?}",
|
||||||
|
command
|
||||||
|
))?
|
||||||
|
.map(FormatOperation::External)
|
||||||
|
}
|
||||||
|
Formatter::CodeActions(code_actions) => {
|
||||||
|
let code_actions = deserialize_code_actions(code_actions);
|
||||||
|
if !code_actions.is_empty() {
|
||||||
|
LspStore::execute_code_actions_on_servers(
|
||||||
|
&lsp_store,
|
||||||
|
adapters_and_servers,
|
||||||
|
code_actions,
|
||||||
|
buffer,
|
||||||
|
push_to_history,
|
||||||
|
transaction,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
anyhow::Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn format_via_external_command(
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
buffer_abs_path: Option<&Path>,
|
||||||
|
command: &str,
|
||||||
|
arguments: &[String],
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Result<Option<Diff>> {
|
||||||
|
let working_dir_path = buffer.update(cx, |buffer, cx| {
|
||||||
|
let file = File::from_dyn(buffer.file())?;
|
||||||
|
let worktree = file.worktree.read(cx);
|
||||||
|
let mut worktree_path = worktree.abs_path().to_path_buf();
|
||||||
|
if worktree.root_entry()?.is_file() {
|
||||||
|
worktree_path.pop();
|
||||||
|
}
|
||||||
|
Some(worktree_path)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut child = smol::process::Command::new(command);
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
use smol::process::windows::CommandExt;
|
||||||
|
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(working_dir_path) = working_dir_path {
|
||||||
|
child.current_dir(working_dir_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child = child
|
||||||
|
.args(arguments.iter().map(|arg| {
|
||||||
|
if let Some(buffer_abs_path) = buffer_abs_path {
|
||||||
|
arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
|
||||||
|
} else {
|
||||||
|
arg.replace("{buffer_path}", "Untitled")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.stdin(smol::process::Stdio::piped())
|
||||||
|
.stdout(smol::process::Stdio::piped())
|
||||||
|
.stderr(smol::process::Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let stdin = child
|
||||||
|
.stdin
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| anyhow!("failed to acquire stdin"))?;
|
||||||
|
let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
|
||||||
|
for chunk in text.chunks() {
|
||||||
|
stdin.write_all(chunk.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
stdin.flush().await?;
|
||||||
|
|
||||||
|
let output = child.output().await?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
|
||||||
|
output.status.code(),
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout)?;
|
||||||
|
Ok(Some(
|
||||||
|
buffer
|
||||||
|
.update(cx, |buffer, cx| buffer.diff(stdout, cx))?
|
||||||
|
.await,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RemoteLspStore {
|
pub struct RemoteLspStore {
|
||||||
|
@ -221,8 +729,6 @@ pub enum LspStoreEvent {
|
||||||
edits: Vec<(lsp::Range, Snippet)>,
|
edits: Vec<(lsp::Range, Snippet)>,
|
||||||
most_recent_edit: clock::Lamport,
|
most_recent_edit: clock::Lamport,
|
||||||
},
|
},
|
||||||
StartFormattingLocalBuffer(BufferId),
|
|
||||||
FinishFormattingLocalBuffer(BufferId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
@ -251,6 +757,7 @@ impl LspStore {
|
||||||
client.add_model_message_handler(Self::handle_start_language_server);
|
client.add_model_message_handler(Self::handle_start_language_server);
|
||||||
client.add_model_message_handler(Self::handle_update_language_server);
|
client.add_model_message_handler(Self::handle_update_language_server);
|
||||||
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
|
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
|
||||||
|
client.add_model_request_handler(Self::handle_format_buffers);
|
||||||
client.add_model_request_handler(Self::handle_resolve_completion_documentation);
|
client.add_model_request_handler(Self::handle_resolve_completion_documentation);
|
||||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||||
client.add_model_request_handler(Self::handle_inlay_hints);
|
client.add_model_request_handler(Self::handle_inlay_hints);
|
||||||
|
@ -366,6 +873,8 @@ impl LspStore {
|
||||||
language_server_watched_paths: Default::default(),
|
language_server_watched_paths: Default::default(),
|
||||||
language_server_watcher_registrations: Default::default(),
|
language_server_watcher_registrations: Default::default(),
|
||||||
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
|
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
|
||||||
|
buffers_being_formatted: Default::default(),
|
||||||
|
last_formatting_failure: None,
|
||||||
prettier_store,
|
prettier_store,
|
||||||
environment,
|
environment,
|
||||||
http_client,
|
http_client,
|
||||||
|
@ -387,6 +896,7 @@ impl LspStore {
|
||||||
diagnostic_summaries: Default::default(),
|
diagnostic_summaries: Default::default(),
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
active_entry: None,
|
active_entry: None,
|
||||||
|
|
||||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||||
}
|
}
|
||||||
|
@ -1276,7 +1786,7 @@ impl LspStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_on_type_formatting(
|
fn apply_on_type_formatting(
|
||||||
&self,
|
&mut self,
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
position: Anchor,
|
position: Anchor,
|
||||||
trigger: String,
|
trigger: String,
|
||||||
|
@ -1298,25 +1808,18 @@ impl LspStore {
|
||||||
.map(language::proto::deserialize_transaction)
|
.map(language::proto::deserialize_transaction)
|
||||||
.transpose()
|
.transpose()
|
||||||
})
|
})
|
||||||
} else {
|
} else if let Some(local) = self.as_local_mut() {
|
||||||
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
|
local.buffers_being_formatted.insert(buffer_id);
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
// Do not allow multiple concurrent formatting requests for the
|
|
||||||
// same buffer.
|
|
||||||
this.update(&mut cx, |_, cx| {
|
|
||||||
cx.emit(LspStoreEvent::StartFormattingLocalBuffer(
|
|
||||||
buffer.read(cx).remote_id(),
|
|
||||||
));
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let _cleanup = defer({
|
let _cleanup = defer({
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
let mut cx = cx.clone();
|
let mut cx = cx.clone();
|
||||||
let closure_buffer = buffer.clone();
|
|
||||||
move || {
|
move || {
|
||||||
this.update(&mut cx, |_, cx| {
|
this.update(&mut cx, |this, _| {
|
||||||
cx.emit(LspStoreEvent::FinishFormattingLocalBuffer(
|
if let Some(local) = this.as_local_mut() {
|
||||||
closure_buffer.read(cx).remote_id(),
|
local.buffers_being_formatted.remove(&buffer_id);
|
||||||
))
|
}
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
@ -1333,6 +1836,8 @@ impl LspStore {
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Err(anyhow!("No upstream client or local language server")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4708,6 +5213,110 @@ impl LspStore {
|
||||||
.map(language::proto::serialize_transaction),
|
.map(language::proto::serialize_transaction),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn last_formatting_failure(&self) -> Option<&str> {
|
||||||
|
self.as_local()
|
||||||
|
.and_then(|local| local.last_formatting_failure.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
&mut self,
|
||||||
|
buffers: HashSet<Model<Buffer>>,
|
||||||
|
push_to_history: bool,
|
||||||
|
trigger: FormatTrigger,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<anyhow::Result<ProjectTransaction>> {
|
||||||
|
if let Some(_) = self.as_local() {
|
||||||
|
let buffers_with_paths = buffers
|
||||||
|
.into_iter()
|
||||||
|
.map(|buffer_handle| {
|
||||||
|
let buffer = buffer_handle.read(cx);
|
||||||
|
let buffer_abs_path = File::from_dyn(buffer.file())
|
||||||
|
.and_then(|file| file.as_local().map(|f| f.abs_path(cx)));
|
||||||
|
(buffer_handle, buffer_abs_path)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
cx.spawn(move |lsp_store, mut cx| async move {
|
||||||
|
let result = LocalLspStore::format_locally(
|
||||||
|
lsp_store.clone(),
|
||||||
|
buffers_with_paths,
|
||||||
|
push_to_history,
|
||||||
|
trigger,
|
||||||
|
cx.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
lsp_store.update(&mut cx, |lsp_store, _| {
|
||||||
|
let local = lsp_store.as_local_mut().unwrap();
|
||||||
|
match &result {
|
||||||
|
Ok(_) => local.last_formatting_failure = None,
|
||||||
|
Err(error) => {
|
||||||
|
local.last_formatting_failure.replace(error.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
result
|
||||||
|
})
|
||||||
|
} else if let Some((client, project_id)) = self.upstream_client() {
|
||||||
|
cx.spawn(move |this, mut cx| async move {
|
||||||
|
let response = client
|
||||||
|
.request(proto::FormatBuffers {
|
||||||
|
project_id,
|
||||||
|
trigger: trigger as i32,
|
||||||
|
buffer_ids: buffers
|
||||||
|
.iter()
|
||||||
|
.map(|buffer| {
|
||||||
|
buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.transaction
|
||||||
|
.ok_or_else(|| anyhow!("missing transaction"))?;
|
||||||
|
BufferStore::deserialize_project_transaction(
|
||||||
|
this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
|
||||||
|
response,
|
||||||
|
push_to_history,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Ok(ProjectTransaction::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_format_buffers(
|
||||||
|
this: Model<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::FormatBuffers>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::FormatBuffersResponse> {
|
||||||
|
let sender_id = envelope.original_sender_id().unwrap_or_default();
|
||||||
|
let format = this.update(&mut cx, |this, cx| {
|
||||||
|
let mut buffers = HashSet::default();
|
||||||
|
for buffer_id in &envelope.payload.buffer_ids {
|
||||||
|
let buffer_id = BufferId::new(*buffer_id)?;
|
||||||
|
buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
|
||||||
|
}
|
||||||
|
let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
|
||||||
|
Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
|
||||||
|
})??;
|
||||||
|
|
||||||
|
let project_transaction = format.await?;
|
||||||
|
let project_transaction = this.update(&mut cx, |this, cx| {
|
||||||
|
this.buffer_store.update(cx, |buffer_store, cx| {
|
||||||
|
buffer_store.serialize_project_transaction_for_peer(
|
||||||
|
project_transaction,
|
||||||
|
sender_id,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(proto::FormatBuffersResponse {
|
||||||
|
transaction: Some(project_transaction),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn language_settings<'a>(
|
fn language_settings<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
|
@ -25,8 +25,8 @@ use smol::stream::StreamExt;
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
worktree_store::WorktreeStore, File, FormatOperation, PathChange, ProjectEntryId, Worktree,
|
lsp_store::WorktreeId, worktree_store::WorktreeStore, File, PathChange, ProjectEntryId,
|
||||||
WorktreeId,
|
Worktree,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PrettierStore {
|
pub struct PrettierStore {
|
||||||
|
@ -644,7 +644,7 @@ pub(super) async fn format_with_prettier(
|
||||||
prettier_store: &WeakModel<PrettierStore>,
|
prettier_store: &WeakModel<PrettierStore>,
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Option<Result<FormatOperation>> {
|
) -> Option<Result<crate::lsp_store::FormatOperation>> {
|
||||||
let prettier_instance = prettier_store
|
let prettier_instance = prettier_store
|
||||||
.update(cx, |prettier_store, cx| {
|
.update(cx, |prettier_store, cx| {
|
||||||
prettier_store.prettier_instance_for_buffer(buffer, cx)
|
prettier_store.prettier_instance_for_buffer(buffer, cx)
|
||||||
|
@ -671,7 +671,7 @@ pub(super) async fn format_with_prettier(
|
||||||
let format_result = prettier
|
let format_result = prettier
|
||||||
.format(buffer, buffer_path, cx)
|
.format(buffer, buffer_path, cx)
|
||||||
.await
|
.await
|
||||||
.map(FormatOperation::Prettier)
|
.map(crate::lsp_store::FormatOperation::Prettier)
|
||||||
.with_context(|| format!("{} failed to format buffer", prettier_description));
|
.with_context(|| format!("{} failed to format buffer", prettier_description));
|
||||||
|
|
||||||
Some(format_result)
|
Some(format_result)
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub use environment::ProjectEnvironment;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{self, UnboundedReceiver},
|
channel::mpsc::{self, UnboundedReceiver},
|
||||||
future::try_join_all,
|
future::try_join_all,
|
||||||
AsyncWriteExt, StreamExt,
|
StreamExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use git::{blame::Blame, repository::GitRepository};
|
use git::{blame::Blame, repository::GitRepository};
|
||||||
|
@ -41,17 +41,14 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{
|
language_settings::InlayHintKind,
|
||||||
language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings,
|
|
||||||
SelectedFormatter,
|
|
||||||
},
|
|
||||||
proto::{
|
proto::{
|
||||||
deserialize_anchor, serialize_anchor, serialize_line_ending, serialize_version,
|
deserialize_anchor, serialize_anchor, serialize_line_ending, serialize_version,
|
||||||
split_operations,
|
split_operations,
|
||||||
},
|
},
|
||||||
Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
|
Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
|
||||||
Diff, Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16,
|
Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset,
|
||||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
ToPointUtf16, Transaction, Unclipped,
|
||||||
};
|
};
|
||||||
use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServerId};
|
use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServerId};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
|
@ -84,7 +81,7 @@ use task::{
|
||||||
};
|
};
|
||||||
use terminals::Terminals;
|
use terminals::Terminals;
|
||||||
use text::{Anchor, BufferId};
|
use text::{Anchor, BufferId};
|
||||||
use util::{defer, paths::compare_paths, ResultExt as _};
|
use util::{paths::compare_paths, ResultExt as _};
|
||||||
use worktree::{CreatedEntry, Snapshot, Traversal};
|
use worktree::{CreatedEntry, Snapshot, Traversal};
|
||||||
use worktree_store::{WorktreeStore, WorktreeStoreEvent};
|
use worktree_store::{WorktreeStore, WorktreeStoreEvent};
|
||||||
|
|
||||||
|
@ -164,8 +161,6 @@ pub struct Project {
|
||||||
search_included_history: SearchHistory,
|
search_included_history: SearchHistory,
|
||||||
search_excluded_history: SearchHistory,
|
search_excluded_history: SearchHistory,
|
||||||
snippets: Model<SnippetProvider>,
|
snippets: Model<SnippetProvider>,
|
||||||
last_formatting_failure: Option<String>,
|
|
||||||
buffers_being_formatted: HashSet<BufferId>,
|
|
||||||
environment: Model<ProjectEnvironment>,
|
environment: Model<ProjectEnvironment>,
|
||||||
settings_observer: Model<SettingsObserver>,
|
settings_observer: Model<SettingsObserver>,
|
||||||
}
|
}
|
||||||
|
@ -477,31 +472,6 @@ impl Hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum FormatTrigger {
|
|
||||||
Save,
|
|
||||||
Manual,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently, formatting operations are represented differently depending on
|
|
||||||
// whether they come from a language server or an external command.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum FormatOperation {
|
|
||||||
Lsp(Vec<(Range<Anchor>, String)>),
|
|
||||||
External(Diff),
|
|
||||||
Prettier(Diff),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FormatTrigger {
|
|
||||||
fn from_proto(value: i32) -> FormatTrigger {
|
|
||||||
match value {
|
|
||||||
0 => FormatTrigger::Save,
|
|
||||||
1 => FormatTrigger::Manual,
|
|
||||||
_ => FormatTrigger::Save,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EntitySubscription {
|
enum EntitySubscription {
|
||||||
Project(PendingEntitySubscription<Project>),
|
Project(PendingEntitySubscription<Project>),
|
||||||
BufferStore(PendingEntitySubscription<BufferStore>),
|
BufferStore(PendingEntitySubscription<BufferStore>),
|
||||||
|
@ -591,7 +561,7 @@ impl Project {
|
||||||
client.add_model_message_handler(Self::handle_update_worktree);
|
client.add_model_message_handler(Self::handle_update_worktree);
|
||||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||||
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
||||||
client.add_model_request_handler(Self::handle_format_buffers);
|
|
||||||
client.add_model_request_handler(Self::handle_search_project);
|
client.add_model_request_handler(Self::handle_search_project);
|
||||||
client.add_model_request_handler(Self::handle_search_candidate_buffers);
|
client.add_model_request_handler(Self::handle_search_candidate_buffers);
|
||||||
client.add_model_request_handler(Self::handle_open_buffer_by_id);
|
client.add_model_request_handler(Self::handle_open_buffer_by_id);
|
||||||
|
@ -695,8 +665,7 @@ impl Project {
|
||||||
search_history: Self::new_search_history(),
|
search_history: Self::new_search_history(),
|
||||||
environment,
|
environment,
|
||||||
remotely_created_models: Default::default(),
|
remotely_created_models: Default::default(),
|
||||||
last_formatting_failure: None,
|
|
||||||
buffers_being_formatted: Default::default(),
|
|
||||||
search_included_history: Self::new_search_history(),
|
search_included_history: Self::new_search_history(),
|
||||||
search_excluded_history: Self::new_search_history(),
|
search_excluded_history: Self::new_search_history(),
|
||||||
}
|
}
|
||||||
|
@ -779,8 +748,7 @@ impl Project {
|
||||||
search_history: Self::new_search_history(),
|
search_history: Self::new_search_history(),
|
||||||
environment,
|
environment,
|
||||||
remotely_created_models: Default::default(),
|
remotely_created_models: Default::default(),
|
||||||
last_formatting_failure: None,
|
|
||||||
buffers_being_formatted: Default::default(),
|
|
||||||
search_included_history: Self::new_search_history(),
|
search_included_history: Self::new_search_history(),
|
||||||
search_excluded_history: Self::new_search_history(),
|
search_excluded_history: Self::new_search_history(),
|
||||||
};
|
};
|
||||||
|
@ -967,8 +935,6 @@ impl Project {
|
||||||
search_excluded_history: Self::new_search_history(),
|
search_excluded_history: Self::new_search_history(),
|
||||||
environment: ProjectEnvironment::new(&worktree_store, None, cx),
|
environment: ProjectEnvironment::new(&worktree_store, None, cx),
|
||||||
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
|
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
|
||||||
last_formatting_failure: None,
|
|
||||||
buffers_being_formatted: Default::default(),
|
|
||||||
};
|
};
|
||||||
this.set_role(role, cx);
|
this.set_role(role, cx);
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
|
@ -2061,12 +2027,6 @@ impl Project {
|
||||||
cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
|
cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LspStoreEvent::StartFormattingLocalBuffer(buffer_id) => {
|
|
||||||
self.buffers_being_formatted.insert(*buffer_id);
|
|
||||||
}
|
|
||||||
LspStoreEvent::FinishFormattingLocalBuffer(buffer_id) => {
|
|
||||||
self.buffers_being_formatted.remove(buffer_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2352,8 +2312,8 @@ impl Project {
|
||||||
self.lsp_store.read(cx).language_server_statuses()
|
self.lsp_store.read(cx).language_server_statuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_formatting_failure(&self) -> Option<&str> {
|
pub fn last_formatting_failure<'a>(&self, cx: &'a AppContext) -> Option<&'a str> {
|
||||||
self.last_formatting_failure.as_deref()
|
self.lsp_store.read(cx).last_formatting_failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
|
@ -2455,558 +2415,12 @@ impl Project {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffers: HashSet<Model<Buffer>>,
|
buffers: HashSet<Model<Buffer>>,
|
||||||
push_to_history: bool,
|
push_to_history: bool,
|
||||||
trigger: FormatTrigger,
|
trigger: lsp_store::FormatTrigger,
|
||||||
cx: &mut ModelContext<Project>,
|
cx: &mut ModelContext<Project>,
|
||||||
) -> Task<anyhow::Result<ProjectTransaction>> {
|
) -> Task<anyhow::Result<ProjectTransaction>> {
|
||||||
if self.is_local_or_ssh() {
|
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||||
let buffers_with_paths = buffers
|
lsp_store.format(buffers, push_to_history, trigger, cx)
|
||||||
.into_iter()
|
})
|
||||||
.map(|buffer_handle| {
|
|
||||||
let buffer = buffer_handle.read(cx);
|
|
||||||
let buffer_abs_path = File::from_dyn(buffer.file())
|
|
||||||
.and_then(|file| file.as_local().map(|f| f.abs_path(cx)));
|
|
||||||
(buffer_handle, buffer_abs_path)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
cx.spawn(move |project, mut cx| async move {
|
|
||||||
let result = Self::format_locally(
|
|
||||||
project.clone(),
|
|
||||||
buffers_with_paths,
|
|
||||||
push_to_history,
|
|
||||||
trigger,
|
|
||||||
cx.clone(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
project.update(&mut cx, |project, _| match &result {
|
|
||||||
Ok(_) => project.last_formatting_failure = None,
|
|
||||||
Err(error) => {
|
|
||||||
project.last_formatting_failure.replace(error.to_string());
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
result
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let remote_id = self.remote_id();
|
|
||||||
let client = self.client.clone();
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
|
||||||
if let Some(project_id) = remote_id {
|
|
||||||
let response = client
|
|
||||||
.request(proto::FormatBuffers {
|
|
||||||
project_id,
|
|
||||||
trigger: trigger as i32,
|
|
||||||
buffer_ids: buffers
|
|
||||||
.iter()
|
|
||||||
.map(|buffer| {
|
|
||||||
buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
|
|
||||||
})
|
|
||||||
.collect::<Result<_>>()?,
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
.transaction
|
|
||||||
.ok_or_else(|| anyhow!("missing transaction"))?;
|
|
||||||
BufferStore::deserialize_project_transaction(
|
|
||||||
this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
|
|
||||||
response,
|
|
||||||
push_to_history,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
Ok(ProjectTransaction::default())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn format_locally(
|
|
||||||
project: WeakModel<Project>,
|
|
||||||
mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
|
|
||||||
push_to_history: bool,
|
|
||||||
trigger: FormatTrigger,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> anyhow::Result<ProjectTransaction> {
|
|
||||||
// Do not allow multiple concurrent formatting requests for the
|
|
||||||
// same buffer.
|
|
||||||
let lsp_store = project.update(&mut cx, |this, cx| {
|
|
||||||
buffers_with_paths.retain(|(buffer, _)| {
|
|
||||||
this.buffers_being_formatted
|
|
||||||
.insert(buffer.read(cx).remote_id())
|
|
||||||
});
|
|
||||||
this.lsp_store.downgrade()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let _cleanup = defer({
|
|
||||||
let this = project.clone();
|
|
||||||
let mut cx = cx.clone();
|
|
||||||
let buffers = &buffers_with_paths;
|
|
||||||
move || {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
for (buffer, _) in buffers {
|
|
||||||
this.buffers_being_formatted
|
|
||||||
.remove(&buffer.read(cx).remote_id());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut project_transaction = ProjectTransaction::default();
|
|
||||||
for (buffer, buffer_abs_path) in &buffers_with_paths {
|
|
||||||
let (primary_adapter_and_server, adapters_and_servers) =
|
|
||||||
project.update(&mut cx, |project, cx| {
|
|
||||||
let buffer = buffer.read(cx);
|
|
||||||
|
|
||||||
let adapters_and_servers = project
|
|
||||||
.language_servers_for_buffer(buffer, cx)
|
|
||||||
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let primary_adapter = project
|
|
||||||
.lsp_store
|
|
||||||
.read(cx)
|
|
||||||
.primary_language_server_for_buffer(buffer, cx)
|
|
||||||
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()));
|
|
||||||
|
|
||||||
(primary_adapter, adapters_and_servers)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let settings = buffer.update(&mut cx, |buffer, cx| {
|
|
||||||
language_settings(buffer.language(), buffer.file(), cx).clone()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
|
|
||||||
let ensure_final_newline = settings.ensure_final_newline_on_save;
|
|
||||||
|
|
||||||
// First, format buffer's whitespace according to the settings.
|
|
||||||
let trailing_whitespace_diff = if remove_trailing_whitespace {
|
|
||||||
Some(
|
|
||||||
buffer
|
|
||||||
.update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
|
|
||||||
buffer.finalize_last_transaction();
|
|
||||||
buffer.start_transaction();
|
|
||||||
if let Some(diff) = trailing_whitespace_diff {
|
|
||||||
buffer.apply_diff(diff, cx);
|
|
||||||
}
|
|
||||||
if ensure_final_newline {
|
|
||||||
buffer.ensure_final_newline(cx);
|
|
||||||
}
|
|
||||||
buffer.end_transaction(cx)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Apply the `code_actions_on_format` before we run the formatter.
|
|
||||||
let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
|
|
||||||
#[allow(clippy::nonminimal_bool)]
|
|
||||||
if !code_actions.is_empty()
|
|
||||||
&& !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
|
|
||||||
{
|
|
||||||
LspStore::execute_code_actions_on_servers(
|
|
||||||
&lsp_store,
|
|
||||||
&adapters_and_servers,
|
|
||||||
code_actions,
|
|
||||||
buffer,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply language-specific formatting using either the primary language server
|
|
||||||
// or external command.
|
|
||||||
// Except for code actions, which are applied with all connected language servers.
|
|
||||||
let primary_language_server =
|
|
||||||
primary_adapter_and_server.map(|(_adapter, server)| server.clone());
|
|
||||||
let server_and_buffer = primary_language_server
|
|
||||||
.as_ref()
|
|
||||||
.zip(buffer_abs_path.as_ref());
|
|
||||||
|
|
||||||
let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
|
|
||||||
language_settings(buffer.language(), buffer.file(), cx)
|
|
||||||
.prettier
|
|
||||||
.clone()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut format_operations: Vec<FormatOperation> = vec![];
|
|
||||||
{
|
|
||||||
match trigger {
|
|
||||||
FormatTrigger::Save => {
|
|
||||||
match &settings.format_on_save {
|
|
||||||
FormatOnSave::Off => {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
FormatOnSave::On => {
|
|
||||||
match &settings.formatter {
|
|
||||||
SelectedFormatter::Auto => {
|
|
||||||
// do the auto-format: prefer prettier, fallback to primary language server
|
|
||||||
let diff = {
|
|
||||||
if prettier_settings.allowed {
|
|
||||||
Self::perform_format(
|
|
||||||
&Formatter::Prettier,
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
Self::perform_format(
|
|
||||||
&Formatter::LanguageServer { name: None },
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
if let Some(op) = diff {
|
|
||||||
format_operations.push(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectedFormatter::List(formatters) => {
|
|
||||||
for formatter in formatters.as_ref() {
|
|
||||||
let diff = Self::perform_format(
|
|
||||||
formatter,
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
if let Some(op) = diff {
|
|
||||||
format_operations.push(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
// format with formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FormatOnSave::List(formatters) => {
|
|
||||||
for formatter in formatters.as_ref() {
|
|
||||||
let diff = Self::perform_format(
|
|
||||||
formatter,
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
if let Some(op) = diff {
|
|
||||||
format_operations.push(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FormatTrigger::Manual => {
|
|
||||||
match &settings.formatter {
|
|
||||||
SelectedFormatter::Auto => {
|
|
||||||
// do the auto-format: prefer prettier, fallback to primary language server
|
|
||||||
let diff = {
|
|
||||||
if prettier_settings.allowed {
|
|
||||||
Self::perform_format(
|
|
||||||
&Formatter::Prettier,
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
Self::perform_format(
|
|
||||||
&Formatter::LanguageServer { name: None },
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
if let Some(op) = diff {
|
|
||||||
format_operations.push(op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectedFormatter::List(formatters) => {
|
|
||||||
for formatter in formatters.as_ref() {
|
|
||||||
// format with formatter
|
|
||||||
let diff = Self::perform_format(
|
|
||||||
formatter,
|
|
||||||
server_and_buffer,
|
|
||||||
project.clone(),
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
&settings,
|
|
||||||
&adapters_and_servers,
|
|
||||||
push_to_history,
|
|
||||||
&mut project_transaction,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
if let Some(op) = diff {
|
|
||||||
format_operations.push(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.update(&mut cx, |b, cx| {
|
|
||||||
// If the buffer had its whitespace formatted and was edited while the language-specific
|
|
||||||
// formatting was being computed, avoid applying the language-specific formatting, because
|
|
||||||
// it can't be grouped with the whitespace formatting in the undo history.
|
|
||||||
if let Some(transaction_id) = whitespace_transaction_id {
|
|
||||||
if b.peek_undo_stack()
|
|
||||||
.map_or(true, |e| e.transaction_id() != transaction_id)
|
|
||||||
{
|
|
||||||
format_operations.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply any language-specific formatting, and group the two formatting operations
|
|
||||||
// in the buffer's undo history.
|
|
||||||
for operation in format_operations {
|
|
||||||
match operation {
|
|
||||||
FormatOperation::Lsp(edits) => {
|
|
||||||
b.edit(edits, None, cx);
|
|
||||||
}
|
|
||||||
FormatOperation::External(diff) => {
|
|
||||||
b.apply_diff(diff, cx);
|
|
||||||
}
|
|
||||||
FormatOperation::Prettier(diff) => {
|
|
||||||
b.apply_diff(diff, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(transaction_id) = whitespace_transaction_id {
|
|
||||||
b.group_until_transaction(transaction_id);
|
|
||||||
} else if let Some(transaction) = project_transaction.0.get(buffer) {
|
|
||||||
b.group_until_transaction(transaction.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(transaction) = b.finalize_last_transaction().cloned() {
|
|
||||||
if !push_to_history {
|
|
||||||
b.forget_transaction(transaction.id);
|
|
||||||
}
|
|
||||||
project_transaction.0.insert(buffer.clone(), transaction);
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(project_transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
async fn perform_format(
|
|
||||||
formatter: &Formatter,
|
|
||||||
primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
|
|
||||||
project: WeakModel<Project>,
|
|
||||||
buffer: &Model<Buffer>,
|
|
||||||
buffer_abs_path: &Option<PathBuf>,
|
|
||||||
settings: &LanguageSettings,
|
|
||||||
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
|
|
||||||
push_to_history: bool,
|
|
||||||
transaction: &mut ProjectTransaction,
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<Option<FormatOperation>, anyhow::Error> {
|
|
||||||
let result = match formatter {
|
|
||||||
Formatter::LanguageServer { name } => {
|
|
||||||
if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
|
|
||||||
let language_server = if let Some(name) = name {
|
|
||||||
adapters_and_servers
|
|
||||||
.iter()
|
|
||||||
.find_map(|(adapter, server)| {
|
|
||||||
adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
|
|
||||||
})
|
|
||||||
.unwrap_or(language_server)
|
|
||||||
} else {
|
|
||||||
language_server
|
|
||||||
};
|
|
||||||
|
|
||||||
let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
|
|
||||||
Some(FormatOperation::Lsp(
|
|
||||||
LspStore::format_via_lsp(
|
|
||||||
&lsp_store,
|
|
||||||
buffer,
|
|
||||||
buffer_abs_path,
|
|
||||||
language_server,
|
|
||||||
settings,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("failed to format via language server")?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Formatter::Prettier => {
|
|
||||||
let prettier = project.update(cx, |project, cx| {
|
|
||||||
project
|
|
||||||
.lsp_store
|
|
||||||
.read(cx)
|
|
||||||
.prettier_store()
|
|
||||||
.unwrap()
|
|
||||||
.downgrade()
|
|
||||||
})?;
|
|
||||||
prettier_store::format_with_prettier(&prettier, buffer, cx)
|
|
||||||
.await
|
|
||||||
.transpose()
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
Formatter::External { command, arguments } => {
|
|
||||||
let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
|
|
||||||
Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)
|
|
||||||
.await
|
|
||||||
.context(format!(
|
|
||||||
"failed to format via external command {:?}",
|
|
||||||
command
|
|
||||||
))?
|
|
||||||
.map(FormatOperation::External)
|
|
||||||
}
|
|
||||||
Formatter::CodeActions(code_actions) => {
|
|
||||||
let code_actions = deserialize_code_actions(code_actions);
|
|
||||||
let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
|
|
||||||
if !code_actions.is_empty() {
|
|
||||||
LspStore::execute_code_actions_on_servers(
|
|
||||||
&lsp_store,
|
|
||||||
adapters_and_servers,
|
|
||||||
code_actions,
|
|
||||||
buffer,
|
|
||||||
push_to_history,
|
|
||||||
transaction,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
anyhow::Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn format_via_external_command(
|
|
||||||
buffer: &Model<Buffer>,
|
|
||||||
buffer_abs_path: Option<&Path>,
|
|
||||||
command: &str,
|
|
||||||
arguments: &[String],
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<Option<Diff>> {
|
|
||||||
let working_dir_path = buffer.update(cx, |buffer, cx| {
|
|
||||||
let file = File::from_dyn(buffer.file())?;
|
|
||||||
let worktree = file.worktree.read(cx);
|
|
||||||
let mut worktree_path = worktree.abs_path().to_path_buf();
|
|
||||||
if worktree.root_entry()?.is_file() {
|
|
||||||
worktree_path.pop();
|
|
||||||
}
|
|
||||||
Some(worktree_path)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut child = smol::process::Command::new(command);
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
use smol::process::windows::CommandExt;
|
|
||||||
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(working_dir_path) = working_dir_path {
|
|
||||||
child.current_dir(working_dir_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut child = child
|
|
||||||
.args(arguments.iter().map(|arg| {
|
|
||||||
if let Some(buffer_abs_path) = buffer_abs_path {
|
|
||||||
arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
|
|
||||||
} else {
|
|
||||||
arg.replace("{buffer_path}", "Untitled")
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.stdin(smol::process::Stdio::piped())
|
|
||||||
.stdout(smol::process::Stdio::piped())
|
|
||||||
.stderr(smol::process::Stdio::piped())
|
|
||||||
.spawn()?;
|
|
||||||
|
|
||||||
let stdin = child
|
|
||||||
.stdin
|
|
||||||
.as_mut()
|
|
||||||
.ok_or_else(|| anyhow!("failed to acquire stdin"))?;
|
|
||||||
let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
|
|
||||||
for chunk in text.chunks() {
|
|
||||||
stdin.write_all(chunk.as_bytes()).await?;
|
|
||||||
}
|
|
||||||
stdin.flush().await?;
|
|
||||||
|
|
||||||
let output = child.output().await?;
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
|
|
||||||
output.status.code(),
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = String::from_utf8(output.stdout)?;
|
|
||||||
Ok(Some(
|
|
||||||
buffer
|
|
||||||
.update(cx, |buffer, cx| buffer.diff(stdout, cx))?
|
|
||||||
.await,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
|
@ -4210,31 +3624,6 @@ impl Project {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_format_buffers(
|
|
||||||
this: Model<Self>,
|
|
||||||
envelope: TypedEnvelope<proto::FormatBuffers>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<proto::FormatBuffersResponse> {
|
|
||||||
let sender_id = envelope.original_sender_id()?;
|
|
||||||
let format = this.update(&mut cx, |this, cx| {
|
|
||||||
let mut buffers = HashSet::default();
|
|
||||||
for buffer_id in &envelope.payload.buffer_ids {
|
|
||||||
let buffer_id = BufferId::new(*buffer_id)?;
|
|
||||||
buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
|
|
||||||
}
|
|
||||||
let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
|
|
||||||
Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
|
|
||||||
})??;
|
|
||||||
|
|
||||||
let project_transaction = format.await?;
|
|
||||||
let project_transaction = this.update(&mut cx, |this, cx| {
|
|
||||||
this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
|
|
||||||
})?;
|
|
||||||
Ok(proto::FormatBuffersResponse {
|
|
||||||
transaction: Some(project_transaction),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_task_context_for_location(
|
async fn handle_task_context_for_location(
|
||||||
project: Model<Self>,
|
project: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::TaskContextForLocation>,
|
envelope: TypedEnvelope<proto::TaskContextForLocation>,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use futures::{future, StreamExt};
|
||||||
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
|
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
|
||||||
use http_client::Url;
|
use http_client::Url;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{AllLanguageSettings, LanguageSettingsContent},
|
language_settings::{language_settings, AllLanguageSettings, LanguageSettingsContent},
|
||||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
|
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
|
||||||
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
|
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue