Detailed changes
@@ -6060,8 +6060,8 @@ dependencies = [
[[package]]
name = "lsp-types"
-version = "0.94.1"
-source = "git+https://github.com/zed-industries/lsp-types?branch=updated-completion-list-item-defaults#90a040a1d195687bd19e1df47463320a44e93d7a"
+version = "0.95.1"
+source = "git+https://github.com/zed-industries/lsp-types?branch=apply-snippet-edit#853c7881d200777e20799026651ca36727144646"
dependencies = [
"bitflags 1.3.2",
"serde",
@@ -7683,6 +7683,7 @@ dependencies = [
"sha2 0.10.7",
"similar",
"smol",
+ "snippet",
"task",
"terminal",
"text",
@@ -13200,9 +13201,9 @@ dependencies = [
[[package]]
name = "zed_html"
-version = "0.0.1"
+version = "0.0.2"
dependencies = [
- "zed_extension_api 0.0.4",
+ "zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -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>,
@@ -1,6 +1,6 @@
[package]
name = "zed_html"
-version = "0.0.1"
+version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"
@@ -13,4 +13,4 @@ path = "src/html.rs"
crate-type = ["cdylib"]
[dependencies]
-zed_extension_api = "0.0.4"
+zed_extension_api = "0.0.6"
@@ -1,7 +1,7 @@
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"
@@ -9,6 +9,15 @@ 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"
@@ -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 = ["-"]
@@ -1,79 +1,93 @@
-use std::{env, fs};
+use std::{env, fs, path::PathBuf};
use zed_extension_api::{self as zed, Result};
-const SERVER_PATH: &str =
- "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
-const PACKAGE_NAME: &str = "vscode-langservers-extracted";
+const PACKAGE_NAME: &str = "vscode-language-server";
struct HtmlExtension {
- did_find_server: bool,
+ path: Option<PathBuf>,
}
impl HtmlExtension {
- fn server_exists(&self) -> bool {
- fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
- }
-
- fn server_script_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
- let server_exists = self.server_exists();
- if self.did_find_server && server_exists {
- return Ok(SERVER_PATH.to_string());
+ fn server_script_path(&self, language_server_id: &zed::LanguageServerId) -> Result<PathBuf> {
+ if let Some(path) = self.path.as_ref() {
+ if fs::metadata(path).map_or(false, |stat| stat.is_dir()) {
+ return Ok(path.clone());
+ }
}
zed::set_language_server_installation_status(
- &config.name,
+ language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
- let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
+ let release = zed::latest_github_release(
+ "zed-industries/vscode-langservers-extracted",
+ zed::GithubReleaseOptions {
+ require_assets: true,
+ pre_release: false,
+ },
+ )?;
- if !server_exists
- || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
- {
+ let asset_name = "vscode-language-server.tar.gz";
+
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
+ let version_dir = format!("{}-{}", PACKAGE_NAME, release.version);
+ if !fs::metadata(&version_dir).map_or(false, |stat| stat.is_dir()) {
zed::set_language_server_installation_status(
- &config.name,
+ &language_server_id,
&zed::LanguageServerInstallationStatus::Downloading,
);
- let result = zed::npm_install_package(PACKAGE_NAME, &version);
- match result {
- Ok(()) => {
- if !self.server_exists() {
- Err(format!(
- "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
- ))?;
- }
- }
- Err(error) => {
- if !self.server_exists() {
- Err(error)?;
- }
+
+ zed::download_file(
+ &asset.download_url,
+ &version_dir,
+ zed::DownloadedFileType::GzipTar,
+ )
+ .map_err(|e| format!("failed to download file: {e}"))?;
+
+ let entries =
+ fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
+ for entry in entries {
+ let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
+ if entry.file_name().to_str() != Some(&version_dir) {
+ fs::remove_dir_all(&entry.path()).ok();
}
}
}
-
- self.did_find_server = true;
- Ok(SERVER_PATH.to_string())
+ Ok(PathBuf::from(version_dir)
+ .join("bin")
+ .join("vscode-html-language-server"))
}
}
impl zed::Extension for HtmlExtension {
fn new() -> Self {
- Self {
- did_find_server: false,
- }
+ Self { path: None }
}
fn language_server_command(
&mut self,
- config: zed::LanguageServerConfig,
+ language_server_id: &zed::LanguageServerId,
_worktree: &zed::Worktree,
) -> Result<zed::Command> {
- let server_path = self.server_script_path(config)?;
+ let path = match &self.path {
+ Some(path) => path,
+ None => {
+ let path = self.server_script_path(language_server_id)?;
+ self.path = Some(path);
+ self.path.as_ref().unwrap()
+ }
+ };
+
Ok(zed::Command {
command: zed::node_binary_path()?,
args: vec![
env::current_dir()
.unwrap()
- .join(&server_path)
+ .join(path)
.to_string_lossy()
.to_string(),
"--stdio".to_string(),