Skip to content

Commit

Permalink
html: Add support for autoclosing of tags (#11761)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
osiewicz authored May 20, 2024
1 parent 0970323 commit 0b8c168
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 66 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions crates/languages/src/javascript/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ["-"]
Expand Down
4 changes: 2 additions & 2 deletions crates/languages/src/tsx/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ["-"]
Expand Down
2 changes: 1 addition & 1 deletion crates/lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/lsp/src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ impl LanguageServer {
ResourceOperationKind::Delete,
]),
document_changes: Some(true),
snippet_edit_support: Some(true),
..WorkspaceEditClientCapabilities::default()
}),
..Default::default()
Expand Down Expand Up @@ -712,6 +713,7 @@ impl LanguageServer {
}
}),
locale: None,
..Default::default()
};

cx.spawn(|_| async move {
Expand Down
1 change: 1 addition & 0 deletions crates/project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 67 additions & 8 deletions crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -332,6 +333,7 @@ pub enum Event {
CollaboratorLeft(proto::PeerId),
RefreshInlayHints,
RevealInProjectPanel(ProjectEntryId),
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
}

pub enum LanguageServerState {
Expand Down Expand Up @@ -2694,7 +2696,6 @@ impl Project {
};

let next_version = previous_snapshot.version + 1;

buffer_snapshots.push(LspBufferSnapshot {
version: next_version,
snapshot: next_snapshot.clone(),
Expand Down Expand Up @@ -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(),
})
}));
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/snippet/src/snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
4 changes: 2 additions & 2 deletions extensions/html/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zed_html"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"
Expand All @@ -13,4 +13,4 @@ path = "src/html.rs"
crate-type = ["cdylib"]

[dependencies]
zed_extension_api = "0.0.4"
zed_extension_api = "0.0.6"
11 changes: 10 additions & 1 deletion extensions/html/extension.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
id = "html"
name = "HTML"
description = "HTML support."
version = "0.0.1"
version = "0.0.2"
schema_version = 1
authors = ["Isaac Clayton <slightknack@gmail.com>"]
repository = "https://github.com/zed-industries/zed"

[language_servers.vscode-html-language-server]
name = "vscode-html-language-server"
language = "HTML"
languages = ["TypeScript", "HTML", "TSX", "JavaScript", "JSDoc"]

[language_servers.vscode-html-language-server.language_ids]
"HTML" = "html"
"PHP" = "php"
"ERB" = "eruby"
"JavaScript" = "javascriptreact"
"TSX" = "typescriptreact"
"CSS" = "css"

[grammars.html]
repository = "https://github.com/tree-sitter/tree-sitter-html"
Expand Down
2 changes: 1 addition & 1 deletion extensions/html/languages/html/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ brackets = [
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "<", end = ">", close = true, newline = true, not_in = ["comment", "string"] },
{ start = "<", end = ">", close = false, newline = true, not_in = ["comment", "string"] },
{ start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
]
word_characters = ["-"]
Expand Down
Loading

0 comments on commit 0b8c168

Please sign in to comment.