Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
Joseph T. Lyons
e7de22f57f Fix Discord text truncation 2023-10-11 01:55:02 -04:00
Joseph T. Lyons
45098e3bd1 Truncate Discord release note text (#3112)
Hopefully this works the first time 😅

Release Notes:

- N/A
2023-10-10 00:08:51 -04:00
Joseph T. Lyons
95bda04978 v0.106.x stable 2023-10-04 15:00:34 -04:00
Mikayla Maki
8d3825f57c
Refactor elixir LSP settings (#3071)
This PR is a bit of a last minute change, but I realized there was
actually a third player in the Elixir LSP space who wants support as
well,
[lexical](https://github.com/zed-industries/community/issues/1567). I
realized that the settings arrangement I shipped in this preview
precludes adding a third kind of LSP. I don't have the time to learn how
to fully support this LSP, but I thought I'd at least refactor how the
settings are represented before this hits stable.

Release Notes:

- Changed the new `"elixir": { "next": "on" }` setting to `"elixir": {
"lsp": "next_ls" }`. The `elixir.lsp` setting requires a full restart to
take effect. (Preview only)
2023-09-29 14:27:52 -07:00
Mikayla
9b9ea8996e
zed 0.106.2 2023-09-29 12:40:33 -07:00
Mikayla Maki
b70b856c7d
Add support for the TextDocumentSyncKind LSP option (#3070)
fixes https://github.com/zed-industries/community/issues/2098

Release Notes:

- Fixed a bug in Zed's LSP implementation when using Next LS.
2023-09-29 12:39:47 -07:00
Antonio Scandurra
348145afdf zed 0.106.1 2023-09-29 16:05:29 +02:00
Antonio Scandurra
59f160a30a Introduce the ability to include or exclude warnings from project diagnostics (#3056)
![CleanShot 2023-09-27 at 18 09
37](https://github.com/zed-industries/zed/assets/482957/317d31e4-81f8-44d8-b94f-8ca7150d3fd2)

Release Notes:

- Added the ability to exclude warnings from project diagnostics. By
default, they will be on but they can be disabled temporarily by
clicking on the warnings icon. The default behavior can be changed by
changing the new `diagnostics.include_warnings` setting.
2023-09-29 16:04:42 +02:00
Joseph T. Lyons
098a6b1aae Enable semantic_index by default (#3061)
Release Notes:

- Enabled the `semantic_index` setting by default.
2023-09-28 17:34:03 -04:00
Conrad Irwin
63db1a97ed Don't prompt to save unchanged files (#3053)
- don't prompt to save a set of unchanged files when closing
(preview-only)
2023-09-27 19:21:19 -06:00
Joseph T. Lyons
e98be4600a v0.106.x preview 2023-09-27 12:26:31 -04:00
17 changed files with 616 additions and 358 deletions

View file

@ -6,8 +6,8 @@ jobs:
discord_release:
runs-on: ubuntu-latest
steps:
- name: Get appropriate URL
id: get-appropriate-url
- name: Get release URL
id: get-release-url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview/latest"
@ -15,14 +15,19 @@ jobs:
URL="https://zed.dev/releases/stable/latest"
fi
echo "::set-output name=URL::$URL"
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.2.0
id: get-content
with:
stringToTruncate: |
📣 Zed ${{ github.event.release.tag_name }} was just released!
Restart your Zed or head to ${{ steps.get-release-url.outputs.URL }} to grab it.
${{ github.event.release.body }}
maxLength: 2000
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: |
📣 Zed ${{ github.event.release.tag_name }} was just released!
Restart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.
${{ github.event.release.body }}
content: ${{ steps.get-content.outputs.string }}

5
Cargo.lock generated
View file

@ -2210,6 +2210,9 @@ dependencies = [
"lsp",
"postage",
"project",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"smallvec",
@ -9831,7 +9834,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.106.0"
version = "0.106.2"
dependencies = [
"activity_indicator",
"anyhow",

View file

@ -227,6 +227,11 @@
},
// Automatically update Zed
"auto_update": true,
// Diagnostics configuration.
"diagnostics": {
// Whether to show warnings or not by default.
"include_warnings": true
},
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:
@ -370,28 +375,28 @@
},
// Difference settings for semantic_index
"semantic_index": {
"enabled": false
"enabled": true
},
// Settings specific to our elixir integration
"elixir": {
// Set Zed to use the experimental Next LS LSP server.
// Change the LSP zed uses for elixir.
// Note that changing this setting requires a restart of Zed
// to take effect.
//
// May take 3 values:
// 1. Use the standard elixir-ls LSP server
// "next": "off"
// 2. Use a bundled version of the next Next LS LSP server
// "next": "on",
// 3. Use a local build of the next Next LS LSP server:
// "next": {
// 1. Use the standard ElixirLS, this is the default
// "lsp": "elixir_ls"
// 2. Use the experimental NextLs
// "lsp": "next_ls",
// 3. Use a language server installed locally on your machine:
// "lsp": {
// "local": {
// "path": "~/next-ls/bin/start",
// "arguments": ["--stdio"]
// }
// },
//
"next": "off"
"lsp": "elixir_ls"
},
// Different settings for specific languages.
"languages": {

View file

@ -21,6 +21,9 @@ util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
postage.workspace = true

View file

@ -1,4 +1,6 @@
pub mod items;
mod project_diagnostics_settings;
mod toolbar_controls;
use anyhow::Result;
use collections::{BTreeSet, HashSet};
@ -19,6 +21,7 @@ use language::{
};
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
use project_diagnostics_settings::ProjectDiagnosticsSettings;
use serde_json::json;
use smallvec::SmallVec;
use std::{
@ -30,18 +33,21 @@ use std::{
sync::Arc,
};
use theme::ThemeSettings;
pub use toolbar_controls::ToolbarControls;
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
};
actions!(diagnostics, [Deploy]);
actions!(diagnostics, [Deploy, ToggleWarnings]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
settings::register::<ProjectDiagnosticsSettings>(cx);
cx.add_action(ProjectDiagnosticsEditor::deploy);
cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
items::init(cx);
}
@ -55,6 +61,7 @@ struct ProjectDiagnosticsEditor {
excerpts: ModelHandle<MultiBuffer>,
path_states: Vec<PathState>,
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
include_warnings: bool,
}
struct PathState {
@ -187,6 +194,7 @@ impl ProjectDiagnosticsEditor {
editor,
path_states: Default::default(),
paths_to_update,
include_warnings: settings::get::<ProjectDiagnosticsSettings>(cx).include_warnings,
};
this.update_excerpts(None, cx);
this
@ -204,6 +212,18 @@ impl ProjectDiagnosticsEditor {
}
}
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
self.include_warnings = !self.include_warnings;
self.paths_to_update = self
.project
.read(cx)
.diagnostic_summaries(cx)
.map(|(path, server_id, _)| (path, server_id))
.collect();
self.update_excerpts(None, cx);
cx.notify();
}
fn update_excerpts(
&mut self,
language_server_id: Option<LanguageServerId>,
@ -277,14 +297,18 @@ impl ProjectDiagnosticsEditor {
let mut blocks_to_add = Vec::new();
let mut blocks_to_remove = HashSet::default();
let mut first_excerpt_id = None;
let max_severity = if self.include_warnings {
DiagnosticSeverity::WARNING
} else {
DiagnosticSeverity::ERROR
};
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
let mut new_groups = snapshot
.diagnostic_groups(language_server_id)
.into_iter()
.filter(|(_, group)| {
group.entries[group.primary_ix].diagnostic.severity
<= DiagnosticSeverity::WARNING
group.entries[group.primary_ix].diagnostic.severity <= max_severity
})
.peekable();
loop {
@ -1501,6 +1525,7 @@ mod tests {
client::init_settings(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
crate::init(cx);
});
}

View file

@ -0,0 +1,28 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct ProjectDiagnosticsSettings {
pub include_warnings: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ProjectDiagnosticsSettingsContent {
include_warnings: Option<bool>,
}
impl settings::Setting for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_cx: &gpui::AppContext,
) -> anyhow::Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
}
}

View file

@ -0,0 +1,115 @@
use crate::{ProjectDiagnosticsEditor, ToggleWarnings};
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
Action, Entity, EventContext, View, ViewContext, WeakViewHandle,
};
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
pub struct ToolbarControls {
editor: Option<WeakViewHandle<ProjectDiagnosticsEditor>>,
}
impl Entity for ToolbarControls {
type Event = ();
}
impl View for ToolbarControls {
fn ui_name() -> &'static str {
"ToolbarControls"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let include_warnings = self
.editor
.as_ref()
.and_then(|editor| editor.upgrade(cx))
.map(|editor| editor.read(cx).include_warnings)
.unwrap_or(false);
let tooltip = if include_warnings {
"Exclude Warnings".into()
} else {
"Include Warnings".into()
};
Flex::row()
.with_child(render_toggle_button(
0,
"icons/warning.svg",
include_warnings,
(tooltip, Some(Box::new(ToggleWarnings))),
cx,
move |this, cx| {
if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
editor.update(cx, |editor, cx| {
editor.toggle_warnings(&Default::default(), cx)
});
}
},
))
.into_any()
}
}
impl ToolbarItemView for ToolbarControls {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(editor.downgrade());
ToolbarItemLocation::PrimaryRight { flex: None }
} else {
ToolbarItemLocation::Hidden
}
} else {
ToolbarItemLocation::Hidden
}
}
}
impl ToolbarControls {
pub fn new() -> Self {
ToolbarControls { editor: None }
}
}
fn render_toggle_button<
F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
>(
index: usize,
icon: &'static str,
toggled: bool,
tooltip: (String, Option<Box<dyn Action>>),
cx: &mut ViewContext<ToolbarControls>,
on_click: F,
) -> AnyElement<ToolbarControls> {
enum Button {}
let theme = theme::current(cx);
let (tooltip_text, action) = tooltip;
MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
let style = theme
.workspace
.toolbar
.toggleable_tool
.in_state(toggled)
.style_for(mouse_state);
Svg::new(icon)
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
.with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
.into_any_named("quick action bar button")
}

View file

@ -2231,26 +2231,62 @@ impl Project {
.get_mut(&buffer.remote_id())
.and_then(|m| m.get_mut(&language_server.server_id()))?;
let previous_snapshot = buffer_snapshots.last()?;
let next_version = previous_snapshot.version + 1;
let content_changes = buffer
.edits_since::<(PointUtf16, usize)>(previous_snapshot.snapshot.version())
.map(|edit| {
let edit_start = edit.new.start.0;
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
let new_text = next_snapshot
.text_for_range(edit.new.start.1..edit.new.end.1)
.collect();
lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(
point_to_lsp(edit_start),
point_to_lsp(edit_end),
)),
let build_incremental_change = || {
buffer
.edits_since::<(PointUtf16, usize)>(
previous_snapshot.snapshot.version(),
)
.map(|edit| {
let edit_start = edit.new.start.0;
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
let new_text = next_snapshot
.text_for_range(edit.new.start.1..edit.new.end.1)
.collect();
lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(
point_to_lsp(edit_start),
point_to_lsp(edit_end),
)),
range_length: None,
text: new_text,
}
})
.collect()
};
let document_sync_kind = language_server
.capabilities()
.text_document_sync
.as_ref()
.and_then(|sync| match sync {
lsp::TextDocumentSyncCapability::Kind(kind) => Some(*kind),
lsp::TextDocumentSyncCapability::Options(options) => options.change,
});
let content_changes: Vec<_> = match document_sync_kind {
Some(lsp::TextDocumentSyncKind::FULL) => {
vec![lsp::TextDocumentContentChangeEvent {
range: None,
range_length: None,
text: new_text,
text: next_snapshot.text(),
}]
}
Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(),
_ => {
#[cfg(any(test, feature = "test-support"))]
{
build_incremental_change()
}
})
.collect();
#[cfg(not(any(test, feature = "test-support")))]
{
continue;
}
}
};
let next_version = previous_snapshot.version + 1;
buffer_snapshots.push(LspBufferSnapshot {
version: next_version,

View file

@ -430,5 +430,9 @@ mod test {
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.simulate_keystrokes([":", "q", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.simulate_keystrokes([":", "q", "a", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
}
}

View file

@ -893,9 +893,13 @@ impl Pane {
) -> Task<Result<()>> {
// Find the items to close.
let mut items_to_close = Vec::new();
let mut dirty_items = Vec::new();
for item in &self.items {
if should_close(item.id()) {
items_to_close.push(item.boxed_clone());
if item.is_dirty(cx) {
dirty_items.push(item.boxed_clone());
}
}
}
@ -907,13 +911,10 @@ impl Pane {
let workspace = self.workspace.clone();
cx.spawn(|pane, mut cx| async move {
if save_intent == SaveIntent::Close && items_to_close.len() > 1 {
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
let mut answer = pane.update(&mut cx, |_, cx| {
let prompt = Self::file_names_for_prompt(
&mut items_to_close.iter(),
items_to_close.len(),
cx,
);
let prompt =
Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
cx.prompt(
PromptLevel::Warning,
&prompt,
@ -921,7 +922,7 @@ impl Pane {
)
})?;
match answer.next().await {
Some(0) => save_intent = SaveIntent::Save,
Some(0) => save_intent = SaveIntent::SaveAll,
Some(1) => save_intent = SaveIntent::Skip,
_ => {}
}
@ -2537,14 +2538,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_inactive_items(&CloseInactiveItems, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_inactive_items(&CloseInactiveItems, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["C*"], cx);
}
@ -2565,12 +2564,10 @@ mod tests {
add_labeled_item(&pane, "E", false, cx);
assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
let task = pane
.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
.unwrap()
.await
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
assert_item_labels(&pane, ["A^", "C*^"], cx);
}
@ -2586,14 +2583,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["C*", "D", "E"], cx);
}
@ -2609,14 +2604,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A", "B", "C*"], cx);
}
@ -2635,14 +2628,28 @@ mod tests {
add_labeled_item(&pane, "C", false, cx);
assert_item_labels(&pane, ["A", "B", "C*"], cx);
let t = pane
pane.update(cx, |pane, cx| {
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, [], cx);
add_labeled_item(&pane, "A", true, cx);
add_labeled_item(&pane, "B", true, cx);
add_labeled_item(&pane, "C", true, cx);
assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
let save = pane
.update(cx, |pane, cx| {
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
t.await.unwrap();
save.await.unwrap();
assert_item_labels(&pane, [], cx);
}

View file

@ -1419,7 +1419,7 @@ impl Workspace {
)
})?;
match answer.next().await {
Some(0) => save_intent = SaveIntent::Save,
Some(0) => save_intent = SaveIntent::SaveAll,
Some(1) => save_intent = SaveIntent::Skip,
_ => {}
}

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.106.0"
version = "0.106.2"
publish = false
[lib]

View file

@ -1 +1 @@
dev
stable

View file

@ -6,12 +6,11 @@ use rust_embed::RustEmbed;
use std::{borrow::Cow, str, sync::Arc};
use util::asset_str;
use self::elixir_next::ElixirSettings;
use self::elixir::ElixirSettings;
mod c;
mod css;
mod elixir;
mod elixir_next;
mod go;
mod html;
mod json;
@ -46,7 +45,7 @@ pub fn init(
node_runtime: Arc<dyn NodeRuntime>,
cx: &mut AppContext,
) {
settings::register::<elixir_next::ElixirSettings>(cx);
settings::register::<elixir::ElixirSettings>(cx);
let language = |name, grammar, adapters| {
languages.register(name, load_config(name), grammar, adapters, load_queries)
@ -72,21 +71,21 @@ pub fn init(
],
);
match &settings::get::<ElixirSettings>(cx).next {
elixir_next::ElixirNextSetting::Off => language(
match &settings::get::<ElixirSettings>(cx).lsp {
elixir::ElixirLspSetting::ElixirLs => language(
"elixir",
tree_sitter_elixir::language(),
vec![Arc::new(elixir::ElixirLspAdapter)],
),
elixir_next::ElixirNextSetting::On => language(
elixir::ElixirLspSetting::NextLs => language(
"elixir",
tree_sitter_elixir::language(),
vec![Arc::new(elixir_next::NextLspAdapter)],
vec![Arc::new(elixir::NextLspAdapter)],
),
elixir_next::ElixirNextSetting::Local { path, arguments } => language(
elixir::ElixirLspSetting::Local { path, arguments } => language(
"elixir",
tree_sitter_elixir::language(),
vec![Arc::new(elixir_next::LocalNextLspAdapter {
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})],

View file

@ -1,12 +1,17 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::{AsyncAppContext, Task};
pub use language::*;
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::Setting;
use smol::fs::{self, File};
use std::{
any::Any,
env::consts,
ops::Deref,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering::SeqCst},
@ -14,11 +19,50 @@ use std::{
},
};
use util::{
async_iife,
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct ElixirSettings {
pub lsp: ElixirLspSetting,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ElixirLspSetting {
ElixirLs,
NextLs,
Local {
path: String,
arguments: Vec<String>,
},
}
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
pub struct ElixirSettingsContent {
lsp: Option<ElixirLspSetting>,
}
impl Setting for ElixirSettings {
const KEY: Option<&'static str> = Some("elixir");
type FileContent = ElixirSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &gpui::AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
}
}
pub struct ElixirLspAdapter;
#[async_trait]
@ -144,14 +188,14 @@ impl LspAdapter for ElixirLspAdapter {
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir).await
get_cached_server_binary_elixir_ls(container_dir).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir).await
get_cached_server_binary_elixir_ls(container_dir).await
}
async fn label_for_completion(
@ -238,7 +282,9 @@ impl LspAdapter for ElixirLspAdapter {
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
async fn get_cached_server_binary_elixir_ls(
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;
@ -254,3 +300,247 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
.await
.log_err()
}
pub struct NextLspAdapter;
#[async_trait]
impl LspAdapter for NextLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("next-ls".into())
}
fn short_name(&self) -> &'static str {
"next-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release =
latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
let version = release.name.clone();
let platform = match consts::ARCH {
"x86_64" => "darwin_arm64",
"aarch64" => "darwin_amd64",
other => bail!("Running on unsupported platform: {other}"),
};
let asset_name = format!("next_ls_{}", platform);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
let version = GitHubLspBinaryVersion {
name: version,
url: asset.browser_download_url.clone(),
};
Ok(Box::new(version) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let binary_path = container_dir.join("next-ls");
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let mut file = smol::fs::File::create(&binary_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
}
Ok(LanguageServerBinary {
path: binary_path,
arguments: vec!["--stdio".into()],
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--stdio".into()];
binary
})
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--help".into()];
binary
})
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol_kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol_kind, language)
}
}
async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<LanguageServerBinary> {
async_iife!({
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_file()
&& entry
.file_name()
.to_str()
.map_or(false, |name| name == "next-ls")
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
arguments: Vec::new(),
})
} else {
Err(anyhow!("no cached binary"))
}
})
.await
.log_err()
}
pub struct LocalLspAdapter {
pub path: String,
pub arguments: Vec<String>,
}
#[async_trait]
impl LspAdapter for LocalLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("local-ls".into())
}
fn short_name(&self) -> &'static str {
"local-ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(()) as Box<_>)
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol, language)
}
}
fn label_for_completion_elixir(
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
return Some(CodeLabel {
runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
text: completion.label.clone(),
filter_range: 0..completion.label.len(),
});
}
fn label_for_symbol_elixir(
name: &str,
_: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
Some(CodeLabel {
runs: language.highlight_text(&name.into(), 0..name.len()),
text: name.to_string(),
filter_range: 0..name.len(),
})
}

View file

@ -1,266 +0,0 @@
use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
pub use language::*;
use lsp::{LanguageServerBinary, SymbolKind};
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::Setting;
use smol::{fs, stream::StreamExt};
use std::{any::Any, env::consts, ops::Deref, path::PathBuf, sync::Arc};
use util::{
async_iife,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct ElixirSettings {
pub next: ElixirNextSetting,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ElixirNextSetting {
Off,
On,
Local {
path: String,
arguments: Vec<String>,
},
}
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
pub struct ElixirSettingsContent {
next: Option<ElixirNextSetting>,
}
impl Setting for ElixirSettings {
const KEY: Option<&'static str> = Some("elixir");
type FileContent = ElixirSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &gpui::AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
}
}
pub struct NextLspAdapter;
#[async_trait]
impl LspAdapter for NextLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("next-ls".into())
}
fn short_name(&self) -> &'static str {
"next-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release =
latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
let version = release.name.clone();
let platform = match consts::ARCH {
"x86_64" => "darwin_arm64",
"aarch64" => "darwin_amd64",
other => bail!("Running on unsupported platform: {other}"),
};
let asset_name = format!("next_ls_{}", platform);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
let version = GitHubLspBinaryVersion {
name: version,
url: asset.browser_download_url.clone(),
};
Ok(Box::new(version) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let binary_path = container_dir.join("next-ls");
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let mut file = smol::fs::File::create(&binary_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
}
Ok(LanguageServerBinary {
path: binary_path,
arguments: vec!["--stdio".into()],
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--stdio".into()];
binary
})
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--help".into()];
binary
})
}
async fn label_for_symbol(
&self,
name: &str,
symbol_kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_next(name, symbol_kind, language)
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
async_iife!({
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_file()
&& entry
.file_name()
.to_str()
.map_or(false, |name| name == "next-ls")
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
arguments: Vec::new(),
})
} else {
Err(anyhow!("no cached binary"))
}
})
.await
.log_err()
}
pub struct LocalNextLspAdapter {
pub path: String,
pub arguments: Vec<String>,
}
#[async_trait]
impl LspAdapter for LocalNextLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("local-next-ls".into())
}
fn short_name(&self) -> &'static str {
"next-ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(()) as Box<_>)
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn label_for_symbol(
&self,
name: &str,
symbol: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_next(name, symbol, language)
}
}
fn label_for_symbol_next(name: &str, _: SymbolKind, language: &Arc<Language>) -> Option<CodeLabel> {
Some(CodeLabel {
runs: language.highlight_text(&name.into(), 0..name.len()),
text: name.to_string(),
filter_range: 0..name.len(),
})
}

View file

@ -275,6 +275,10 @@ pub fn initialize_workspace(
QuickActionBar::new(buffer_search_bar, workspace)
});
toolbar.add_item(quick_action_bar, cx);
let diagnostic_editor_controls = cx.add_view(|_| {
diagnostics::ToolbarControls::new()
});
toolbar.add_item(diagnostic_editor_controls, cx);
let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
toolbar.add_item(project_search_bar, cx);
let submit_feedback_button =