Detailed changes
@@ -746,8 +746,6 @@
"default_width": 380
},
"agent": {
- // Version of this setting.
- "version": "2",
// Whether the agent is enabled.
"enabled": true,
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
@@ -1658,7 +1656,6 @@
// Different settings for specific language models.
"language_models": {
"anthropic": {
- "version": "1",
"api_url": "https://api.anthropic.com"
},
"google": {
@@ -1668,7 +1665,6 @@
"api_url": "http://localhost:11434"
},
"openai": {
- "version": "1",
"api_url": "https://api.openai.com/v1"
},
"open_router": {
@@ -379,6 +379,14 @@ impl ConfigureContextServerModal {
};
self.state = State::Waiting;
+
+ let existing_server = self.context_server_store.read(cx).get_running_server(&id);
+ if existing_server.is_some() {
+ self.context_server_store.update(cx, |store, cx| {
+ store.stop_server(&id, cx).log_err();
+ });
+ }
+
let wait_for_context_server_task =
wait_for_context_server(&self.context_server_store, id.clone(), cx);
cx.spawn({
@@ -399,13 +407,21 @@ impl ConfigureContextServerModal {
})
.detach();
- // When we write the settings to the file, the context server will be restarted.
- workspace.update(cx, |workspace, cx| {
- let fs = workspace.app_state().fs.clone();
- update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
- project_settings.context_servers.insert(id.0, settings);
+ let settings_changed =
+ ProjectSettings::get_global(cx).context_servers.get(&id.0) != Some(&settings);
+
+ if settings_changed {
+ // When we write the settings to the file, the context server will be restarted.
+ workspace.update(cx, |workspace, cx| {
+ let fs = workspace.app_state().fs.clone();
+ update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
+ project_settings.context_servers.insert(id.0, settings);
+ });
});
- });
+ } else if let Some(existing_server) = existing_server {
+ self.context_server_store
+ .update(cx, |store, cx| store.start_server(existing_server, cx));
+ }
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
@@ -25,9 +25,7 @@ fn schema_to_json(
fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> Schema {
let mut generator = match format {
LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(),
- // TODO: Gemini docs mention using a subset of OpenAPI 3, so this may benefit from using
- // `SchemaSettings::openapi3()`.
- LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::draft07()
+ LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::openapi3()
.with(|settings| {
settings.meta_schema = None;
settings.inline_subschemas = true;
@@ -5,7 +5,7 @@ use super::{
use alacritty_terminal::vte::ansi;
use anyhow::Result;
use collections::HashMap;
-use dap::OutputEvent;
+use dap::{CompletionItem, CompletionItemType, OutputEvent};
use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
use fuzzy::StringMatchCandidate;
use gpui::{
@@ -17,6 +17,7 @@ use menu::{Confirm, SelectNext, SelectPrevious};
use project::{
Completion, CompletionResponse,
debugger::session::{CompletionsQuery, OutputToken, Session},
+ lsp_store::CompletionDocumentation,
search_history::{SearchHistory, SearchHistoryCursor},
};
use settings::Settings;
@@ -555,15 +556,27 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider {
buffer: &Entity<Buffer>,
position: language::Anchor,
text: &str,
- _trigger_in_words: bool,
+ trigger_in_words: bool,
menu_is_open: bool,
cx: &mut Context<Editor>,
) -> bool {
+ let mut chars = text.chars();
+ let char = if let Some(char) = chars.next() {
+ char
+ } else {
+ return false;
+ };
+
let snapshot = buffer.read(cx).snapshot();
if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
return false;
}
+ let classifier = snapshot.char_classifier_at(position).for_completion(true);
+ if trigger_in_words && classifier.is_word(char) {
+ return true;
+ }
+
self.0
.read_with(cx, |console, cx| {
console
@@ -596,21 +609,28 @@ impl ConsoleQueryBarCompletionProvider {
variable_list.completion_variables(cx)
}) {
if let Some(evaluate_name) = &variable.evaluate_name {
- variables.insert(evaluate_name.clone(), variable.value.clone());
+ if variables
+ .insert(evaluate_name.clone(), variable.value.clone())
+ .is_none()
+ {
+ string_matches.push(StringMatchCandidate {
+ id: 0,
+ string: evaluate_name.clone(),
+ char_bag: evaluate_name.chars().collect(),
+ });
+ }
+ }
+
+ if variables
+ .insert(variable.name.clone(), variable.value.clone())
+ .is_none()
+ {
string_matches.push(StringMatchCandidate {
id: 0,
- string: evaluate_name.clone(),
- char_bag: evaluate_name.chars().collect(),
+ string: variable.name.clone(),
+ char_bag: variable.name.chars().collect(),
});
}
-
- variables.insert(variable.name.clone(), variable.value.clone());
-
- string_matches.push(StringMatchCandidate {
- id: 0,
- string: variable.name.clone(),
- char_bag: variable.name.chars().collect(),
- });
}
(variables, string_matches)
@@ -656,11 +676,13 @@ impl ConsoleQueryBarCompletionProvider {
new_text: string_match.string.clone(),
label: CodeLabel {
filter_range: 0..string_match.string.len(),
- text: format!("{} {}", string_match.string, variable_value),
+ text: string_match.string.clone(),
runs: Vec::new(),
},
icon_path: None,
- documentation: None,
+ documentation: Some(CompletionDocumentation::MultiLineMarkdown(
+ variable_value.into(),
+ )),
confirm: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
@@ -675,6 +697,32 @@ impl ConsoleQueryBarCompletionProvider {
})
}
+ const fn completion_type_score(completion_type: CompletionItemType) -> usize {
+ match completion_type {
+ CompletionItemType::Field | CompletionItemType::Property => 0,
+ CompletionItemType::Variable | CompletionItemType::Value => 1,
+ CompletionItemType::Method
+ | CompletionItemType::Function
+ | CompletionItemType::Constructor => 2,
+ CompletionItemType::Class
+ | CompletionItemType::Interface
+ | CompletionItemType::Module => 3,
+ _ => 4,
+ }
+ }
+
+ fn completion_item_sort_text(completion_item: &CompletionItem) -> String {
+ completion_item.sort_text.clone().unwrap_or_else(|| {
+ format!(
+ "{:03}_{}",
+ Self::completion_type_score(
+ completion_item.type_.unwrap_or(CompletionItemType::Text)
+ ),
+ completion_item.label.to_ascii_lowercase()
+ )
+ })
+ }
+
fn client_completions(
&self,
console: &Entity<Console>,
@@ -699,6 +747,7 @@ impl ConsoleQueryBarCompletionProvider {
let completions = completions
.into_iter()
.map(|completion| {
+ let sort_text = Self::completion_item_sort_text(&completion);
let new_text = completion
.text
.as_ref()
@@ -731,12 +780,11 @@ impl ConsoleQueryBarCompletionProvider {
runs: Vec::new(),
},
icon_path: None,
- documentation: None,
+ documentation: completion.detail.map(|detail| {
+ CompletionDocumentation::MultiLineMarkdown(detail.into())
+ }),
confirm: None,
- source: project::CompletionSource::BufferWord {
- word_range: buffer_position..language::Anchor::MAX,
- resolved: false,
- },
+ source: project::CompletionSource::Dap { sort_text },
insert_text_mode: None,
}
})
@@ -1083,11 +1083,10 @@ impl CompletionsMenu {
if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
);
- let sort_text = if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source
- {
- lsp_completion.sort_text.as_deref()
- } else {
- None
+ let sort_text = match &completion.source {
+ CompletionSource::Lsp { lsp_completion, .. } => lsp_completion.sort_text.as_deref(),
+ CompletionSource::Dap { sort_text } => Some(sort_text.as_str()),
+ _ => None,
};
let (sort_kind, sort_label) = completion.sort_key();
@@ -1,5 +1,6 @@
use crate::{
- ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version,
+ ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, build_debug_adapter_schema_path,
+ parse_wasm_extension_version,
};
use anyhow::{Context as _, Result, bail};
use async_compression::futures::bufread::GzipDecoder;
@@ -99,12 +100,8 @@ impl ExtensionBuilder {
}
for (debug_adapter_name, meta) in &mut extension_manifest.debug_adapters {
- let debug_adapter_relative_schema_path =
- meta.schema_path.clone().unwrap_or_else(|| {
- Path::new("debug_adapter_schemas")
- .join(Path::new(debug_adapter_name.as_ref()).with_extension("json"))
- });
- let debug_adapter_schema_path = extension_dir.join(debug_adapter_relative_schema_path);
+ let debug_adapter_schema_path =
+ extension_dir.join(build_debug_adapter_schema_path(debug_adapter_name, meta));
let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path)
.with_context(|| {
@@ -132,6 +132,16 @@ impl ExtensionManifest {
}
}
+pub fn build_debug_adapter_schema_path(
+ adapter_name: &Arc<str>,
+ meta: &DebugAdapterManifestEntry,
+) -> PathBuf {
+ meta.schema_path.clone().unwrap_or_else(|| {
+ Path::new("debug_adapter_schemas")
+ .join(Path::new(adapter_name.as_ref()).with_extension("json"))
+ })
+}
+
/// A capability for an extension.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
@@ -320,6 +330,29 @@ mod tests {
}
}
+ #[test]
+ fn test_build_adapter_schema_path_with_schema_path() {
+ let adapter_name = Arc::from("my_adapter");
+ let entry = DebugAdapterManifestEntry {
+ schema_path: Some(PathBuf::from("foo/bar")),
+ };
+
+ let path = build_debug_adapter_schema_path(&adapter_name, &entry);
+ assert_eq!(path, PathBuf::from("foo/bar"));
+ }
+
+ #[test]
+ fn test_build_adapter_schema_path_without_schema_path() {
+ let adapter_name = Arc::from("my_adapter");
+ let entry = DebugAdapterManifestEntry { schema_path: None };
+
+ let path = build_debug_adapter_schema_path(&adapter_name, &entry);
+ assert_eq!(
+ path,
+ PathBuf::from("debug_adapter_schemas").join("my_adapter.json")
+ );
+ }
+
#[test]
fn test_allow_exact_match() {
let manifest = ExtensionManifest {
@@ -1639,6 +1639,23 @@ impl ExtensionStore {
}
}
+ for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() {
+ let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta);
+
+ if fs.is_file(&src_dir.join(schema_path)).await {
+ match schema_path.parent() {
+ Some(parent) => fs.create_dir(&tmp_dir.join(parent)).await?,
+ None => {}
+ }
+ fs.copy_file(
+ &src_dir.join(schema_path),
+ &tmp_dir.join(schema_path),
+ fs::CopyOptions::default(),
+ )
+ .await?
+ }
+ }
+
Ok(())
})
}
@@ -4,8 +4,8 @@ use anyhow::{Context as _, Result};
use client::{TypedEnvelope, proto};
use collections::{HashMap, HashSet};
use extension::{
- Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
- ExtensionManifest,
+ Extension, ExtensionDebugAdapterProviderProxy, ExtensionHostProxy, ExtensionLanguageProxy,
+ ExtensionLanguageServerProxy, ExtensionManifest,
};
use fs::{Fs, RemoveOptions, RenameOptions};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
@@ -169,8 +169,9 @@ impl HeadlessExtensionStore {
return Ok(());
}
- let wasm_extension: Arc<dyn Extension> =
- Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?);
+ let wasm_extension: Arc<dyn Extension> = Arc::new(
+ WasmExtension::load(extension_dir.clone(), &manifest, wasm_host.clone(), &cx).await?,
+ );
for (language_server_id, language_server_config) in &manifest.language_servers {
for language in language_server_config.languages() {
@@ -186,6 +187,24 @@ impl HeadlessExtensionStore {
);
})?;
}
+ for (debug_adapter, meta) in &manifest.debug_adapters {
+ let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, meta);
+
+ this.update(cx, |this, _cx| {
+ this.proxy.register_debug_adapter(
+ wasm_extension.clone(),
+ debug_adapter.clone(),
+ &extension_dir.join(schema_path),
+ );
+ })?;
+ }
+
+ for debug_adapter in manifest.debug_locators.keys() {
+ this.update(cx, |this, _cx| {
+ this.proxy
+ .register_debug_locator(wasm_extension.clone(), debug_adapter.clone());
+ })?;
+ }
}
Ok(())
@@ -200,8 +200,8 @@ impl<P: LinuxClient + 'static> Platform for P {
app_path = app_path.display()
);
- // execute the script using /bin/bash
- let restart_process = Command::new("/bin/bash")
+ let restart_process = Command::new("/usr/bin/env")
+ .arg("bash")
.arg("-c")
.arg(script)
.process_group(0)
@@ -1935,12 +1935,14 @@ impl Session {
}
pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
+ let supports_single_thread_execution_requests =
+ self.capabilities.supports_single_thread_execution_requests;
self.thread_states.continue_thread(thread_id);
self.request(
ContinueCommand {
args: ContinueArguments {
thread_id: thread_id.0,
- single_thread: Some(true),
+ single_thread: supports_single_thread_execution_requests,
},
},
Self::on_step_response::<ContinueCommand>(thread_id),
@@ -6043,7 +6043,9 @@ impl LspStore {
);
server.request::<lsp::request::ResolveCompletionItem>(*lsp_completion.clone())
}
- CompletionSource::BufferWord { .. } | CompletionSource::Custom => {
+ CompletionSource::BufferWord { .. }
+ | CompletionSource::Dap { .. }
+ | CompletionSource::Custom => {
return Ok(());
}
}
@@ -6195,7 +6197,9 @@ impl LspStore {
}
serde_json::to_string(lsp_completion).unwrap().into_bytes()
}
- CompletionSource::Custom | CompletionSource::BufferWord { .. } => {
+ CompletionSource::Custom
+ | CompletionSource::Dap { .. }
+ | CompletionSource::BufferWord { .. } => {
return Ok(());
}
}
@@ -11081,6 +11085,10 @@ impl LspStore {
serialized_completion.source = proto::completion::Source::Custom as i32;
serialized_completion.resolved = true;
}
+ CompletionSource::Dap { sort_text } => {
+ serialized_completion.source = proto::completion::Source::Dap as i32;
+ serialized_completion.sort_text = Some(sort_text.clone());
+ }
}
serialized_completion
@@ -11135,6 +11143,11 @@ impl LspStore {
resolved: completion.resolved,
}
}
+ Some(proto::completion::Source::Dap) => CompletionSource::Dap {
+ sort_text: completion
+ .sort_text
+ .context("expected sort text to exist")?,
+ },
_ => anyhow::bail!("Unexpected completion source {}", completion.source),
},
})
@@ -456,6 +456,10 @@ pub enum CompletionSource {
/// Whether this completion has been resolved, to ensure it happens once per completion.
resolved: bool,
},
+ Dap {
+ /// The sort text for this completion.
+ sort_text: String,
+ },
Custom,
BufferWord {
word_range: Range<Anchor>,
@@ -3303,12 +3303,13 @@ impl ProjectPanel {
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, GitEntryRef<'_>)> {
let mut offset = 0;
for (worktree_id, visible_worktree_entries, _) in &self.visible_entries {
- if visible_worktree_entries.len() > offset + index {
+ let current_len = visible_worktree_entries.len();
+ if index < offset + current_len {
return visible_worktree_entries
- .get(index)
+ .get(index - offset)
.map(|entry| (*worktree_id, entry.to_ref()));
}
- offset += visible_worktree_entries.len();
+ offset += current_len;
}
None
}
@@ -222,11 +222,13 @@ message Completion {
optional Anchor buffer_word_end = 10;
Anchor old_insert_start = 11;
Anchor old_insert_end = 12;
+ optional string sort_text = 13;
enum Source {
Lsp = 0;
Custom = 1;
BufferWord = 2;
+ Dap = 3;
}
}
@@ -170,6 +170,12 @@ impl<T: AsRef<Path>> From<T> for SanitizedPath {
pub const FILE_ROW_COLUMN_DELIMITER: char = ':';
const ROW_COL_CAPTURE_REGEX: &str = r"(?xs)
+ ([^\(]+)\:(?:
+ \((\d+)[,:](\d+)\) # filename:(row,column), filename:(row:column)
+ |
+ \((\d+)\)() # filename:(row)
+ )
+ |
([^\(]+)(?:
\((\d+)[,:](\d+)\) # filename(row,column), filename(row:column)
|
@@ -674,6 +680,15 @@ mod tests {
column: None
}
);
+
+ assert_eq!(
+ PathWithPosition::parse_str("Types.hs:(617,9)-(670,28):"),
+ PathWithPosition {
+ path: PathBuf::from("Types.hs"),
+ row: Some(617),
+ column: Some(9),
+ }
+ );
}
#[test]
@@ -256,7 +256,7 @@ In order to do that, you need to configure the language server so that it knows
"tailwindcss-language-server": {
"settings": {
"includeLanguages": {
- "erb": "html",
+ "html/erb": "html",
"ruby": "html"
},
"experimental": {
@@ -379,3 +379,22 @@ The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name
}
]
```
+
+## Formatters
+
+### `erb-formatter`
+
+To format ERB templates, you can use the `erb-formatter` formatter. This formatter uses the [`erb-formatter`](https://rubygems.org/gems/erb-formatter) gem to format ERB templates.
+
+```jsonc
+{
+ "HTML/ERB": {
+ "formatter": {
+ "external": {
+ "command": "erb-formatter",
+ "arguments": ["--stdin-filename", "{buffer_path}"],
+ },
+ },
+ },
+}
+```
@@ -9,12 +9,13 @@ repository = "https://github.com/zed-industries/zed"
[language_servers.emmet-language-server]
name = "Emmet Language Server"
language = "HTML"
-languages = ["HTML", "PHP", "ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir"]
+languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir"]
[language_servers.emmet-language-server.language_ids]
"HTML" = "html"
"PHP" = "php"
"ERB" = "eruby"
+"HTML/ERB" = "eruby"
"JavaScript" = "javascriptreact"
"TSX" = "typescriptreact"
"CSS" = "css"