html: Add support for autoclosing of tags (#11761)
Fixes #5267 TODO: - [x] Publish our fork of vscode-langservers-extracted on GH and wire that through as a language server of choice for HTML extension. - [x] Figure out how to prevent edits made by remote participants from moving the cursor of a host. Release Notes: - Added support for autoclosing of HTML tags in local projects.
This commit is contained in:
parent
097032327d
commit
0b8c1680fb
13 changed files with 173 additions and 75 deletions
|
@ -1587,7 +1587,21 @@ impl Editor {
|
|||
project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
|
||||
if let project::Event::RefreshInlayHints = event {
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
|
||||
};
|
||||
} else if let project::Event::SnippetEdit(id, snippet_edits) = event {
|
||||
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
if focus_handle.is_focused(cx) {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
for (range, snippet) in snippet_edits {
|
||||
let editor_range =
|
||||
language::range_from_lsp(*range).to_offset(&snapshot);
|
||||
editor
|
||||
.insert_snippet(&[editor_range], snippet.clone(), cx)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
let task_inventory = project.read(cx).task_inventory().clone();
|
||||
project_subscriptions.push(cx.observe(&task_inventory, |editor, _, cx| {
|
||||
|
@ -1601,7 +1615,6 @@ impl Editor {
|
|||
&buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||
|
@ -10728,7 +10741,6 @@ impl Editor {
|
|||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(EditorEvent::Focused);
|
||||
|
||||
if let Some(rename) = self.pending_rename.as_ref() {
|
||||
let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
|
||||
cx.focus(&rename_editor_focus_handle);
|
||||
|
|
|
@ -16,12 +16,12 @@ brackets = [
|
|||
]
|
||||
word_characters = ["$", "#"]
|
||||
tab_size = 2
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server","vscode-html-language-server", "emmet-language-server"]
|
||||
|
||||
[overrides.element]
|
||||
line_comments = { remove = true }
|
||||
block_comment = ["{/* ", " */}"]
|
||||
opt_into_language_servers = ["emmet-language-server"]
|
||||
opt_into_language_servers = ["emmet-language-server", "vscode-html-language-server"]
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
|
|
|
@ -14,13 +14,13 @@ brackets = [
|
|||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
word_characters = ["#", "$"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"]
|
||||
scope_opt_in_language_servers = ["vscode-html-language-server", "tailwindcss-language-server", "emmet-language-server"]
|
||||
tab_size = 2
|
||||
|
||||
[overrides.element]
|
||||
line_comments = { remove = true }
|
||||
block_comment = ["{/* ", " */}"]
|
||||
opt_into_language_servers = ["emmet-language-server"]
|
||||
opt_into_language_servers = ["vscode-html-language-server", "emmet-language-server"]
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
|
|
|
@ -22,7 +22,7 @@ collections.workspace = true
|
|||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "updated-completion-list-item-defaults" }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "apply-snippet-edit" }
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -601,6 +601,7 @@ impl LanguageServer {
|
|||
ResourceOperationKind::Delete,
|
||||
]),
|
||||
document_changes: Some(true),
|
||||
snippet_edit_support: Some(true),
|
||||
..WorkspaceEditClientCapabilities::default()
|
||||
}),
|
||||
..Default::default()
|
||||
|
@ -712,6 +713,7 @@ impl LanguageServer {
|
|||
}
|
||||
}),
|
||||
locale: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
cx.spawn(|_| async move {
|
||||
|
|
|
@ -58,6 +58,7 @@ settings.workspace = true
|
|||
sha2.workspace = true
|
||||
similar = "1.3"
|
||||
smol.workspace = true
|
||||
snippet.workspace = true
|
||||
terminal.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
|
|
|
@ -55,9 +55,9 @@ use language::{
|
|||
use log::error;
|
||||
use lsp::{
|
||||
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId,
|
||||
DocumentHighlightKind, Edit, LanguageServer, LanguageServerBinary, LanguageServerId,
|
||||
LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus,
|
||||
ServerStatus,
|
||||
ServerStatus, TextEdit,
|
||||
};
|
||||
use lsp_command::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
|
@ -67,6 +67,7 @@ use prettier_support::{DefaultPrettier, PrettierInstance};
|
|||
use project_settings::{LspSettings, ProjectSettings};
|
||||
use rand::prelude::*;
|
||||
use search_history::SearchHistory;
|
||||
use snippet::Snippet;
|
||||
use worktree::LocalSnapshot;
|
||||
|
||||
use http::{HttpClient, Url};
|
||||
|
@ -332,6 +333,7 @@ pub enum Event {
|
|||
CollaboratorLeft(proto::PeerId),
|
||||
RefreshInlayHints,
|
||||
RevealInProjectPanel(ProjectEntryId),
|
||||
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
|
||||
}
|
||||
|
||||
pub enum LanguageServerState {
|
||||
|
@ -2694,7 +2696,6 @@ impl Project {
|
|||
};
|
||||
|
||||
let next_version = previous_snapshot.version + 1;
|
||||
|
||||
buffer_snapshots.push(LspBufferSnapshot {
|
||||
version: next_version,
|
||||
snapshot: next_snapshot.clone(),
|
||||
|
@ -6209,7 +6210,7 @@ impl Project {
|
|||
uri,
|
||||
version: None,
|
||||
},
|
||||
edits: edits.into_iter().map(OneOf::Left).collect(),
|
||||
edits: edits.into_iter().map(Edit::Plain).collect(),
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
@ -6287,7 +6288,7 @@ impl Project {
|
|||
let buffer_to_edit = this
|
||||
.update(cx, |this, cx| {
|
||||
this.open_local_buffer_via_lsp(
|
||||
op.text_document.uri,
|
||||
op.text_document.uri.clone(),
|
||||
language_server.server_id(),
|
||||
lsp_adapter.name.clone(),
|
||||
cx,
|
||||
|
@ -6297,10 +6298,68 @@ impl Project {
|
|||
|
||||
let edits = this
|
||||
.update(cx, |this, cx| {
|
||||
let edits = op.edits.into_iter().map(|edit| match edit {
|
||||
OneOf::Left(edit) => edit,
|
||||
OneOf::Right(edit) => edit.text_edit,
|
||||
let path = buffer_to_edit.read(cx).project_path(cx);
|
||||
let active_entry = this.active_entry;
|
||||
let is_active_entry = path.clone().map_or(false, |project_path| {
|
||||
this.entry_for_path(&project_path, cx)
|
||||
.map_or(false, |entry| Some(entry.id) == active_entry)
|
||||
});
|
||||
|
||||
let (mut edits, mut snippet_edits) = (vec![], vec![]);
|
||||
for edit in op.edits {
|
||||
match edit {
|
||||
Edit::Plain(edit) => edits.push(edit),
|
||||
Edit::Annotated(edit) => edits.push(edit.text_edit),
|
||||
Edit::Snippet(edit) => {
|
||||
let Ok(snippet) = Snippet::parse(&edit.snippet.value)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_active_entry {
|
||||
snippet_edits.push((edit.range, snippet));
|
||||
} else {
|
||||
// Since this buffer is not focused, apply a normal edit.
|
||||
edits.push(TextEdit {
|
||||
range: edit.range,
|
||||
new_text: snippet.text,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !snippet_edits.is_empty() {
|
||||
if let Some(buffer_version) = op.text_document.version {
|
||||
let buffer_id = buffer_to_edit.read(cx).remote_id();
|
||||
// Check if the edit that triggered that edit has been made by this participant.
|
||||
let should_apply_edit = this
|
||||
.buffer_snapshots
|
||||
.get(&buffer_id)
|
||||
.and_then(|server_to_snapshots| {
|
||||
let all_snapshots = server_to_snapshots
|
||||
.get(&language_server.server_id())?;
|
||||
all_snapshots
|
||||
.binary_search_by_key(&buffer_version, |snapshot| {
|
||||
snapshot.version
|
||||
})
|
||||
.ok()
|
||||
.and_then(|index| all_snapshots.get(index))
|
||||
})
|
||||
.map_or(false, |lsp_snapshot| {
|
||||
let version = lsp_snapshot.snapshot.version();
|
||||
let most_recent_edit = version
|
||||
.iter()
|
||||
.max_by_key(|timestamp| timestamp.value);
|
||||
most_recent_edit.map_or(false, |edit| {
|
||||
edit.replica_id == this.replica_id()
|
||||
})
|
||||
});
|
||||
if should_apply_edit {
|
||||
cx.emit(Event::SnippetEdit(buffer_id, snippet_edits));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.edits_from_lsp(
|
||||
&buffer_to_edit,
|
||||
edits,
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
|
|||
use smallvec::SmallVec;
|
||||
use std::{collections::BTreeMap, ops::Range};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Snippet {
|
||||
pub text: String,
|
||||
pub tabstops: Vec<TabStop>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue