Compare commits

..

5 Commits

Author SHA1 Message Date
Conrad Irwin
203c4b2406 Finish the thought 2024-07-22 10:55:52 -06:00
Conrad Irwin
33dcfbbc4a Tidy 2024-07-21 23:09:13 -06:00
Conrad Irwin
c758744687 Tidy 2024-07-21 21:19:21 -06:00
Conrad Irwin
8d3c7b6eb8 Add vim tests for multichar bindings
Co-Authored-By: @haruleekim
2024-07-21 21:15:12 -06:00
Conrad Irwin
2c53899c5d COMPILIGN 2024-07-21 20:31:52 -06:00
83 changed files with 877 additions and 1624 deletions

View File

@@ -1,14 +0,0 @@
name: "Dependency Review"
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Dependency Review"
uses: actions/dependency-review-action@v4

View File

@@ -251,8 +251,8 @@ jobs:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-macos-x86_64.gz
target/zed-remote-server-macos-aarch64.gz
target/zed-remote-server-mac-x86_64.gz
target/zed-remote-server-mac-aarch64.gz
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg

11
Cargo.lock generated
View File

@@ -4013,7 +4013,6 @@ dependencies = [
"env_logger",
"extension",
"fs",
"http 0.1.0",
"language",
"log",
"rpc",
@@ -5316,7 +5315,6 @@ name = "http"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"futures 0.3.28",
"futures-lite 1.13.0",
"isahc",
@@ -13910,7 +13908,7 @@ dependencies = [
name = "zed_erlang"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6",
"zed_extension_api 0.0.4",
]
[[package]]
@@ -14013,13 +14011,6 @@ dependencies = [
"zed_extension_api 0.0.6",
]
[[package]]
name = "zed_ruff"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6",
]
[[package]]
name = "zed_snippets"
version = "0.0.5"

View File

@@ -141,7 +141,6 @@ members = [
"extensions/php",
"extensions/prisma",
"extensions/purescript",
"extensions/ruff",
"extensions/ruby",
"extensions/snippets",
"extensions/svelte",

View File

@@ -606,7 +606,6 @@
"ctrl-alt-space": "terminal::ShowCharacterPalette",
"shift-ctrl-c": "terminal::Copy",
"ctrl-insert": "terminal::Copy",
"ctrl-a": "editor::SelectAll",
"shift-ctrl-v": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",

View File

@@ -628,7 +628,6 @@
"ctrl-cmd-space": "terminal::ShowCharacterPalette",
"cmd-c": "terminal::Copy",
"cmd-v": "terminal::Paste",
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"ctrl-enter": "assistant::InlineAssist",
// Some nice conveniences

View File

@@ -705,7 +705,7 @@
//
"file_types": {
"JSON": ["flake.lock"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "tsconfig.json"]
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json"]
},
// The extensions that Zed should automatically install on startup.
//
@@ -743,9 +743,6 @@
"Elixir": {
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
},
"Erlang": {
"language_servers": ["erlang-ls", "!elp", "..."]
},
"Go": {
"code_actions_on_format": {
"source.organizeImports": true
@@ -815,9 +812,6 @@
"plugins": ["prettier-plugin-sql"]
}
},
"Starlark": {
"language_servers": ["starpls", "!buck2-lsp", "..."]
},
"Svelte": {
"prettier": {
"allowed": true,

View File

@@ -20,12 +20,6 @@ pub enum Model {
Claude3Sonnet,
#[serde(alias = "claude-3-haiku", rename = "claude-3-haiku-20240307")]
Claude3Haiku,
#[serde(rename = "custom")]
Custom {
name: String,
#[serde(default)]
max_tokens: Option<usize>,
},
}
impl Model {
@@ -39,41 +33,30 @@ impl Model {
} else if id.starts_with("claude-3-haiku") {
Ok(Self::Claude3Haiku)
} else {
Ok(Self::Custom {
name: id.to_string(),
max_tokens: None,
})
Err(anyhow!("Invalid model id: {}", id))
}
}
pub fn id(&self) -> &str {
pub fn id(&self) -> &'static str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
Model::Claude3Opus => "claude-3-opus-20240229",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
Model::Claude3Haiku => "claude-3-opus-20240307",
Model::Custom { name, .. } => name,
}
}
pub fn display_name(&self) -> &str {
pub fn display_name(&self) -> &'static str {
match self {
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Custom { name, .. } => name,
}
}
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200_000,
Self::Custom { max_tokens, .. } => max_tokens.unwrap_or(200_000),
}
200_000
}
}
@@ -107,7 +90,6 @@ impl From<Role> for String {
#[derive(Debug, Serialize)]
pub struct Request {
#[serde(serialize_with = "serialize_request_model")]
pub model: Model,
pub messages: Vec<RequestMessage>,
pub stream: bool,
@@ -115,13 +97,6 @@ pub struct Request {
pub max_tokens: u32,
}
fn serialize_request_model<S>(model: &Model, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&model.id())
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct RequestMessage {
pub role: Role,

View File

@@ -668,11 +668,7 @@ mod tests {
"version": "1",
"provider": {
"name": "zed.dev",
"default_model": {
"custom": {
"name": "custom-provider"
}
}
"default_model": "custom"
}
}
}"#,
@@ -683,10 +679,7 @@ mod tests {
assert_eq!(
AssistantSettings::get_global(cx).provider,
AssistantProvider::ZedDotDev {
model: CloudModel::Custom {
name: "custom-provider".into(),
max_tokens: None
}
model: CloudModel::Custom("custom".into())
}
);
}

View File

@@ -22,9 +22,9 @@ use futures::{
SinkExt, Stream, StreamExt,
};
use gpui::{
point, AppContext, EventEmitter, FocusHandle, FocusableView, Global, HighlightStyle, Model,
ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
WindowContext,
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
WhiteSpace, WindowContext,
};
use language::{Buffer, Point, Selection, TransactionId};
use language_model::{LanguageModelRequest, LanguageModelRequestMessage, Role};
@@ -45,7 +45,7 @@ use std::{
time::{Duration, Instant},
};
use theme::ThemeSettings;
use ui::{prelude::*, ContextMenu, IconButtonShape, PopoverMenu, Tooltip};
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
use util::RangeExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -1461,7 +1461,7 @@ impl Render for PromptEditor {
})
.trigger(
IconButton::new("context", IconName::Settings)
.shape(IconButtonShape::Square)
.size(ButtonSize::None)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
@@ -1473,7 +1473,7 @@ impl Render for PromptEditor {
.display_name()
),
None,
"Change Model",
"Click to Change Model",
cx,
)
}),
@@ -1864,8 +1864,12 @@ impl PromptEditor {
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.editor,

View File

@@ -13,8 +13,8 @@ use editor::{
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
AppContext, Context, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, WeakView, WhiteSpace,
};
use language::Buffer;
use language_model::{LanguageModelRequest, LanguageModelRequestMessage, Role};
@@ -27,7 +27,7 @@ use std::{
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{prelude::*, ContextMenu, IconButtonShape, PopoverMenu, Tooltip};
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -588,7 +588,7 @@ impl Render for PromptEditor {
})
.trigger(
IconButton::new("context", IconName::Settings)
.shape(IconButtonShape::Square)
.size(ButtonSize::None)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
@@ -600,7 +600,7 @@ impl Render for PromptEditor {
.display_name()
),
None,
"Change Model",
"Click to Change Model",
cx,
)
}),
@@ -945,9 +945,13 @@ impl PromptEditor {
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.editor,

View File

@@ -28,7 +28,7 @@ use std::{
consts::{ARCH, OS},
},
ffi::OsString,
path::{Path, PathBuf},
path::PathBuf,
sync::Arc,
time::Duration,
};
@@ -401,15 +401,7 @@ impl AutoUpdater {
release_channel = ReleaseChannel::Nightly;
}
let release = Self::get_latest_release(
&this,
"zed-remote-server",
os,
arch,
Some(release_channel),
cx,
)
.await?;
let release = Self::get_latest_release(&this, "zed-remote-server", os, arch, cx).await?;
let servers_dir = paths::remote_servers_dir();
let channel_dir = servers_dir.join(release_channel.dev_name());
@@ -431,7 +423,6 @@ impl AutoUpdater {
asset: &str,
os: &str,
arch: &str,
release_channel: Option<ReleaseChannel>,
cx: &mut AsyncAppContext,
) -> Result<JsonRelease> {
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
@@ -439,10 +430,14 @@ impl AutoUpdater {
"/api/releases/latest?asset={}&os={}&arch={}",
asset, os, arch
));
if let Some(param) = release_channel.and_then(|c| c.release_query_param()) {
url_string += "&";
url_string += param;
}
cx.update(|cx| {
if let Some(param) = ReleaseChannel::try_global(cx)
.and_then(|release_channel| release_channel.release_query_param())
{
url_string += "&";
url_string += param;
}
})?;
let mut response = client.get(&url_string, Default::default(), true).await?;
@@ -453,34 +448,17 @@ impl AutoUpdater {
.await
.context("error reading release")?;
if !response.status().is_success() {
Err(anyhow!(
"failed to fetch release: {:?}",
String::from_utf8_lossy(&body),
))?;
}
serde_json::from_slice(body.as_slice()).with_context(|| {
format!(
"error deserializing release {:?}",
String::from_utf8_lossy(&body),
)
})
serde_json::from_slice(body.as_slice()).context("error deserializing release")
}
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
let (client, current_version, release_channel) = this.update(&mut cx, |this, cx| {
let (client, current_version) = this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Checking;
cx.notify();
(
this.http_client.clone(),
this.current_version,
ReleaseChannel::try_global(cx),
)
(this.http_client.clone(), this.current_version)
})?;
let release =
Self::get_latest_release(&this, "zed", OS, ARCH, release_channel, &mut cx).await?;
let release = Self::get_latest_release(&this, "zed", OS, ARCH, &mut cx).await?;
let should_download = match *RELEASE_CHANNEL {
ReleaseChannel::Nightly => cx
@@ -507,14 +485,7 @@ impl AutoUpdater {
let temp_dir = tempfile::Builder::new()
.prefix("zed-auto-update")
.tempdir()?;
let filename = match OS {
"macos" => Ok("Zed.dmg"),
"linux" => Ok("zed.tar.gz"),
_ => Err(anyhow!("not supported: {:?}", OS)),
}?;
let downloaded_asset = temp_dir.path().join(filename);
download_release(&downloaded_asset, release, client, &cx).await?;
let downloaded_asset = download_release(&temp_dir, release, "zed", client, &cx).await?;
this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Installing;
@@ -595,11 +566,13 @@ async fn download_remote_server_binary(
}
async fn download_release(
target_path: &Path,
temp_dir: &tempfile::TempDir,
release: JsonRelease,
target_filename: &str,
client: Arc<HttpClientWithUrl>,
cx: &AsyncAppContext,
) -> Result<()> {
) -> Result<PathBuf> {
let target_path = temp_dir.path().join(target_filename);
let mut target_file = File::create(&target_path).await?;
let (installation_id, release_channel, telemetry) = cx.update(|cx| {
@@ -621,7 +594,7 @@ async fn download_release(
smol::io::copy(response.body_mut(), &mut target_file).await?;
log::info!("downloaded update. path:{:?}", target_path);
Ok(())
Ok(target_path)
}
async fn install_release_linux(

View File

@@ -4514,7 +4514,7 @@ impl RateLimit for CompleteWithLanguageModelRateLimit {
}
async fn complete_with_language_model(
mut request: proto::CompleteWithLanguageModel,
request: proto::CompleteWithLanguageModel,
response: StreamingResponse<proto::CompleteWithLanguageModel>,
session: Session,
open_ai_api_key: Option<Arc<str>>,
@@ -4530,43 +4530,18 @@ async fn complete_with_language_model(
.check::<CompleteWithLanguageModelRateLimit>(session.user_id())
.await?;
let mut provider_and_model = request.model.split('/');
let (provider, model) = match (
provider_and_model.next().unwrap(),
provider_and_model.next(),
) {
(provider, Some(model)) => (provider, model),
(model, None) => {
if model.starts_with("gpt") {
("openai", model)
} else if model.starts_with("gemini") {
("google", model)
} else if model.starts_with("claude") {
("anthropic", model)
} else {
("unknown", model)
}
}
};
let provider = provider.to_string();
request.model = model.to_string();
match provider.as_str() {
"openai" => {
let api_key = open_ai_api_key.context("no OpenAI API key configured on the server")?;
complete_with_open_ai(request, response, session, api_key).await?;
}
"anthropic" => {
let api_key =
anthropic_api_key.context("no Anthropic AI API key configured on the server")?;
complete_with_anthropic(request, response, session, api_key).await?;
}
"google" => {
let api_key =
google_ai_api_key.context("no Google AI API key configured on the server")?;
complete_with_google_ai(request, response, session, api_key).await?;
}
provider => return Err(anyhow!("unknown provider {:?}", provider))?,
if request.model.starts_with("gpt") {
let api_key =
open_ai_api_key.ok_or_else(|| anyhow!("no OpenAI API key configured on the server"))?;
complete_with_open_ai(request, response, session, api_key).await?;
} else if request.model.starts_with("gemini") {
let api_key = google_ai_api_key
.ok_or_else(|| anyhow!("no Google AI API key configured on the server"))?;
complete_with_google_ai(request, response, session, api_key).await?;
} else if request.model.starts_with("claude") {
let api_key = anthropic_api_key
.ok_or_else(|| anyhow!("no Anthropic AI API key configured on the server"))?;
complete_with_anthropic(request, response, session, api_key).await?;
}
Ok(())

View File

@@ -6,7 +6,7 @@ use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorSt
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
Render, Task, TextStyle, View, ViewContext, WeakView,
Render, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
};
use language::{
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
@@ -537,7 +537,10 @@ impl Render for MessageEditor {
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
div()

View File

@@ -16,7 +16,7 @@ use gpui::{
EventEmitter, FocusHandle, FocusableView, FontStyle, InteractiveElement, IntoElement,
ListOffset, ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
Render, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext,
WeakView,
WeakView, WhiteSpace,
};
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
use project::{Fs, Project};
@@ -2194,7 +2194,10 @@ impl CollabPanel {
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(

View File

@@ -4,7 +4,7 @@ use anthropic::{stream_completion, Model as AnthropicModel, Request, RequestMess
use anyhow::{anyhow, Result};
use editor::{Editor, EditorElement, EditorStyle};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, Task, TextStyle, View};
use gpui::{AnyView, AppContext, FontStyle, Task, TextStyle, View, WhiteSpace};
use http::HttpClient;
use language_model::Role;
use settings::Settings;
@@ -257,8 +257,12 @@ impl AuthenticationPrompt {
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.api_key,

View File

@@ -54,15 +54,15 @@ impl CloudCompletionProvider {
impl LanguageModelCompletionProvider for CloudCompletionProvider {
fn available_models(&self) -> Vec<LanguageModel> {
let mut custom_model = if matches!(self.model, CloudModel::Custom { .. }) {
Some(self.model.clone())
let mut custom_model = if let CloudModel::Custom(custom_model) = self.model.clone() {
Some(custom_model)
} else {
None
};
CloudModel::iter()
.filter_map(move |model| {
if let CloudModel::Custom { .. } = model {
custom_model.take()
if let CloudModel::Custom(_) = model {
Some(CloudModel::Custom(custom_model.take()?))
} else {
Some(model)
}
@@ -117,9 +117,9 @@ impl LanguageModelCompletionProvider for CloudCompletionProvider {
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
count_open_ai_tokens(request, cx.background_executor())
}
LanguageModel::Cloud(CloudModel::Custom { name, .. }) => {
LanguageModel::Cloud(CloudModel::Custom(model)) => {
let request = self.client.request(proto::CountTokensWithLanguageModel {
model: name,
model,
messages: request
.messages
.iter()

View File

@@ -3,7 +3,7 @@ use crate::LanguageModelCompletionProvider;
use anyhow::{anyhow, Result};
use editor::{Editor, EditorElement, EditorStyle};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, Task, TextStyle, View};
use gpui::{AnyView, AppContext, FontStyle, Task, TextStyle, View, WhiteSpace};
use http::HttpClient;
use language_model::{CloudModel, LanguageModel, LanguageModelRequest, Role};
use open_ai::Model as OpenAiModel;
@@ -241,7 +241,6 @@ pub fn count_open_ai_tokens(
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
| LanguageModel::Cloud(CloudModel::Custom { .. })
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
@@ -299,8 +298,12 @@ impl AuthenticationPrompt {
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.api_key,

View File

@@ -954,7 +954,6 @@ fn random_diagnostic(
is_primary,
is_disk_based: false,
is_unnecessary: false,
data: None,
},
}
}

View File

@@ -2,14 +2,19 @@ use futures::Future;
use git::blame::BlameEntry;
use git::Oid;
use gpui::{
Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle, StatefulInteractiveElement,
WeakView, WindowContext,
Asset, Element, ParentElement, Render, ScrollHandle, StatefulInteractiveElement, WeakView,
WindowContext,
};
use settings::Settings;
use std::hash::Hash;
use theme::ThemeSettings;
use theme::{ActiveTheme, ThemeSettings};
use time::UtcOffset;
use ui::{prelude::*, tooltip_container, Avatar};
use ui::{
div, h_flex, tooltip_container, v_flex, Avatar, Button, ButtonStyle, Clickable as _, Color,
FluentBuilder, Icon, IconName, IconPosition, InteractiveElement as _, IntoElement,
SharedString, Styled as _, ViewContext,
};
use ui::{ButtonCommon, Disableable as _};
use workspace::Workspace;
use crate::git::blame::{CommitDetails, GitRemote};
@@ -125,7 +130,6 @@ impl Render for BlameEntryTooltip {
let author_email = self.blame_entry.author_mail.clone();
let short_commit_id = self.blame_entry.sha.display_short();
let full_sha = self.blame_entry.sha.to_string().clone();
let absolute_timestamp = blame_entry_absolute_timestamp(&self.blame_entry);
let message = self
@@ -236,16 +240,6 @@ impl Render for BlameEntryTooltip {
})
},
),
)
.child(
IconButton::new("copy-sha-button", IconName::Copy)
.icon_color(Color::Muted)
.on_click(move |_, cx| {
cx.stop_propagation();
cx.write_to_clipboard(ClipboardItem::new(
full_sha.clone(),
))
}),
),
),
),

View File

@@ -136,12 +136,12 @@ impl From<BlockId> for EntityId {
}
}
impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
impl Into<ElementId> for BlockId {
fn into(self) -> ElementId {
match self {
Self::Custom(CustomBlockId(id)) => ("Block", id).into(),
Self::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
Self::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
}
}
}

View File

@@ -69,11 +69,11 @@ use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
WeakView, WindowContext,
FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
WeakFocusHandle, WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -12438,8 +12438,12 @@ impl Render for Editor {
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
},
EditorMode::Full => TextStyle {
color: cx.theme().colors().editor_foreground,
@@ -12447,8 +12451,12 @@ impl Render for Editor {
font_features: settings.buffer_font.features.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
font_style: FontStyle::Normal,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
},
};

View File

@@ -64,10 +64,10 @@ struct CargoTomlPackage {
}
impl ExtensionBuilder {
pub fn new(http_client: Arc<dyn HttpClient>, cache_dir: PathBuf) -> Self {
pub fn new(cache_dir: PathBuf) -> Self {
Self {
cache_dir,
http: http_client,
http: http::client(None),
}
}

View File

@@ -243,10 +243,7 @@ impl ExtensionStore {
extension_index: Default::default(),
installed_dir,
index_path,
builder: Arc::new(ExtensionBuilder::new(
http::client(http_client.proxy().cloned()),
build_dir,
)),
builder: Arc::new(ExtensionBuilder::new(build_dir)),
outstanding_operations: Default::default(),
modified_extensions: Default::default(),
reload_complete_senders: Vec::new(),

View File

@@ -16,9 +16,8 @@ path = "src/main.rs"
anyhow.workspace = true
clap = { workspace = true, features = ["derive"] }
env_logger.workspace = true
extension = { workspace = true, features = ["no-webrtc"] }
fs.workspace = true
http.workspace = true
extension = { workspace = true, features = ["no-webrtc"] }
language.workspace = true
log.workspace = true
rpc.workspace = true

View File

@@ -13,7 +13,6 @@ use extension::{
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
ExtensionManifest,
};
use http::HttpClientWithProxy;
use language::LanguageConfig;
use theme::ThemeRegistry;
use tree_sitter::{Language, Query, WasmStore};
@@ -59,9 +58,7 @@ async fn main() -> Result<()> {
let mut manifest = ExtensionManifest::load(fs.clone(), &extension_path).await?;
log::info!("compiling extension");
let http_client = Arc::new(HttpClientWithProxy::new(None));
let builder = ExtensionBuilder::new(http_client, scratch_dir);
let builder = ExtensionBuilder::new(scratch_dir);
builder
.compile_extension(
&extension_path,

View File

@@ -14,9 +14,9 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, uniform_list, AppContext, EventEmitter, Flatten, FocusableView, InteractiveElement,
KeyContext, ParentElement, Render, Styled, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
actions, uniform_list, AppContext, EventEmitter, Flatten, FocusableView, FontStyle,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
};
use num_format::{Locale, ToFormattedString};
use project::DirectoryLister;
@@ -804,8 +804,12 @@ impl ExtensionsPage {
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(

View File

@@ -375,7 +375,6 @@ impl Asset for Image {
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
return Err(ImageCacheError::BadStatus {
uri,
status: response.status(),
body: String::from_utf8_lossy(&body).into_owned(),
});
@@ -418,10 +417,8 @@ pub enum ImageCacheError {
#[error("IO error: {0}")]
Io(Arc<std::io::Error>),
/// An error that occurred while processing an image.
#[error("unexpected http status for {uri}: {status}, body: {body}")]
#[error("unexpected http status: {status}, body: {body}")]
BadStatus {
/// The URI of the image.
uri: SharedUri,
/// The HTTP status code.
status: http::StatusCode,
/// The HTTP response body.

View File

@@ -90,7 +90,9 @@ impl Keymap {
/// bindings are evaluated with the same precedence rules so you can disable a rule in
/// a given context only.
///
/// In the case of multi-key bindings, the
/// In the case where a binding conflicts with a longer binding, precedence is resolved
/// only using the order in the keymap file. So binding `cmd-k` in the workspace will disable
/// built-in bindings for `cmd-k X` throughout the app.
pub fn bindings_for_input(
&self,
input: &[Keystroke],

View File

@@ -17,7 +17,6 @@ doctest = true
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true
isahc.workspace = true
log.workspace = true

View File

@@ -1,7 +1,6 @@
pub mod github;
pub use anyhow::{anyhow, Result};
use derive_more::Deref;
use futures::future::BoxFuture;
use futures_lite::FutureExt;
use isahc::config::{Configurable, RedirectPolicy};
@@ -17,119 +16,61 @@ use std::{
};
pub use url::Url;
pub trait HttpClient: Send + Sync {
fn send(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>;
fn get<'a>(
&'a self,
uri: &str,
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.redirect_policy(if follow_redirects {
RedirectPolicy::Follow
} else {
RedirectPolicy::None
})
.method(Method::GET)
.uri(uri)
.body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
}
fn get_proxy(proxy: Option<String>) -> Option<isahc::http::Uri> {
macro_rules! try_env {
($($env:literal),+) => {
$(
if let Ok(env) = std::env::var($env) {
return env.parse::<isahc::http::Uri>().ok();
}
)+
};
}
fn post_json<'a>(
&'a self,
uri: &str,
body: AsyncBody,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.method(Method::POST)
.uri(uri)
.header("Content-Type", "application/json")
.body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
}
}
fn proxy(&self) -> Option<&Uri>;
}
/// An [`HttpClient`] that may have a proxy.
#[derive(Deref)]
pub struct HttpClientWithProxy {
#[deref]
client: Arc<dyn HttpClient>,
proxy: Option<Uri>,
}
impl HttpClientWithProxy {
/// Returns a new [`HttpClientWithProxy`] with the given proxy URL.
pub fn new(proxy_url: Option<String>) -> Self {
let proxy_url = proxy_url
.and_then(|input| {
input
.parse::<Uri>()
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
.ok()
})
.or_else(read_proxy_from_env);
Self {
client: client(proxy_url.clone()),
proxy: proxy_url,
}
}
}
impl HttpClient for HttpClientWithProxy {
fn send(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
self.proxy.as_ref()
}
}
impl HttpClient for Arc<HttpClientWithProxy> {
fn send(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
self.proxy.as_ref()
}
proxy
.and_then(|input| {
input
.parse::<isahc::http::Uri>()
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
.ok()
})
.or_else(|| {
try_env!(
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy"
);
None
})
}
/// An [`HttpClient`] that has a base URL.
pub struct HttpClientWithUrl {
base_url: Mutex<String>,
client: HttpClientWithProxy,
client: Arc<dyn HttpClient>,
proxy: Option<String>,
}
impl HttpClientWithUrl {
/// Returns a new [`HttpClientWithUrl`] with the given base URL.
pub fn new(base_url: impl Into<String>, proxy_url: Option<String>) -> Self {
let client = HttpClientWithProxy::new(proxy_url);
pub fn new(base_url: impl Into<String>, unparsed_proxy: Option<String>) -> Self {
let parsed_proxy = get_proxy(unparsed_proxy);
let proxy_string = parsed_proxy.as_ref().map(|p| {
// Map proxy settings from `http://localhost:10809` to `http://127.0.0.1:10809`
// NodeRuntime without environment information can not parse `localhost`
// correctly.
// TODO: map to `[::1]` if we are using ipv6
p.to_string()
.to_ascii_lowercase()
.replace("localhost", "127.0.0.1")
});
Self {
base_url: Mutex::new(base_url.into()),
client,
client: client(parsed_proxy),
proxy: proxy_string,
}
}
@@ -181,8 +122,8 @@ impl HttpClient for Arc<HttpClientWithUrl> {
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
self.client.proxy.as_ref()
fn proxy(&self) -> Option<&str> {
self.proxy.as_deref()
}
}
@@ -194,42 +135,66 @@ impl HttpClient for HttpClientWithUrl {
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
self.client.proxy.as_ref()
fn proxy(&self) -> Option<&str> {
self.proxy.as_deref()
}
}
pub fn client(proxy: Option<Uri>) -> Arc<dyn HttpClient> {
Arc::new(HttpClientWithProxy {
client: Arc::new(
isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(proxy.clone())
.build()
.unwrap(),
),
proxy,
})
}
pub trait HttpClient: Send + Sync {
fn send(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>;
fn read_proxy_from_env() -> Option<Uri> {
const ENV_VARS: &[&str] = &[
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy",
];
for var in ENV_VARS {
if let Ok(env) = std::env::var(var) {
return env.parse::<Uri>().ok();
fn get<'a>(
&'a self,
uri: &str,
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.redirect_policy(if follow_redirects {
RedirectPolicy::Follow
} else {
RedirectPolicy::None
})
.method(Method::GET)
.uri(uri)
.body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
}
}
None
fn post_json<'a>(
&'a self,
uri: &str,
body: AsyncBody,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.method(Method::POST)
.uri(uri)
.header("Content-Type", "application/json")
.body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
}
}
fn proxy(&self) -> Option<&str>;
}
pub fn client(proxy: Option<isahc::http::Uri>) -> Arc<dyn HttpClient> {
Arc::new(
isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(proxy)
.build()
.unwrap(),
)
}
impl HttpClient for isahc::HttpClient {
@@ -241,7 +206,7 @@ impl HttpClient for isahc::HttpClient {
Box::pin(async move { client.send_async(req).await })
}
fn proxy(&self) -> Option<&Uri> {
fn proxy(&self) -> Option<&str> {
None
}
}
@@ -268,12 +233,10 @@ impl FakeHttpClient {
{
Arc::new(HttpClientWithUrl {
base_url: Mutex::new("http://test.example".into()),
client: HttpClientWithProxy {
client: Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))),
}),
proxy: None,
},
client: Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))),
}),
proxy: None,
})
}
@@ -313,7 +276,7 @@ impl HttpClient for FakeHttpClient {
Box::pin(async move { future.await.map(Into::into) })
}
fn proxy(&self) -> Option<&Uri> {
fn proxy(&self) -> Option<&str> {
None
}
}

View File

@@ -298,9 +298,7 @@ impl InlineCompletionButton {
fn build_supermaven_context_menu(&self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
ContextMenu::build(cx, |menu, cx| {
self.build_language_settings_menu(menu, cx)
.separator()
.action("Sign Out", supermaven::SignOut.boxed_clone())
self.build_language_settings_menu(menu, cx).separator()
})
}

View File

@@ -27,7 +27,6 @@ use gpui::{
use lazy_static::lazy_static;
use lsp::LanguageServerId;
use parking_lot::Mutex;
use serde_json::Value;
use similar::{ChangeTag, TextDiff};
use smallvec::SmallVec;
use smol::future::yield_now;
@@ -214,8 +213,6 @@ pub struct Diagnostic {
pub is_disk_based: bool,
/// Whether this diagnostic marks unnecessary code.
pub is_unnecessary: bool,
/// Data from language server that produced this diagnostic. Passed back to the LS when we request code actions for this diagnostic.
pub data: Option<Value>,
}
/// TODO - move this into the `project` crate and make it private.
@@ -3847,7 +3844,6 @@ impl Default for Diagnostic {
is_primary: false,
is_disk_based: false,
is_unnecessary: false,
data: None,
}
}
}

View File

@@ -69,7 +69,6 @@ impl DiagnosticEntry<PointUtf16> {
severity: Some(self.diagnostic.severity),
source: self.diagnostic.source.clone(),
message: self.diagnostic.message.clone(),
data: self.diagnostic.data.clone(),
..Default::default()
}
}

View File

@@ -1,5 +1,8 @@
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{relative, AppContext, BackgroundExecutor, HighlightStyle, StyledText, TextStyle};
use gpui::{
relative, AppContext, BackgroundExecutor, FontStyle, HighlightStyle, StyledText, TextStyle,
WhiteSpace,
};
use settings::Settings;
use std::ops::Range;
use theme::{color_alpha, ActiveTheme, ThemeSettings};
@@ -164,8 +167,12 @@ pub fn render_item<T>(
font_features: settings.buffer_font.features.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
let highlights = gpui::combine_highlights(
custom_highlights,

View File

@@ -5,8 +5,7 @@ use anyhow::{anyhow, Context as _, Result};
use clock::ReplicaId;
use lsp::{DiagnosticSeverity, LanguageServerId};
use rpc::proto;
use serde_json::Value;
use std::{ops::Range, str::FromStr, sync::Arc};
use std::{ops::Range, sync::Arc};
use text::*;
pub use proto::{BufferState, Operation};
@@ -214,7 +213,6 @@ pub fn serialize_diagnostics<'a>(
code: entry.diagnostic.code.clone(),
is_disk_based: entry.diagnostic.is_disk_based,
is_unnecessary: entry.diagnostic.is_unnecessary,
data: entry.diagnostic.data.as_ref().map(|data| data.to_string()),
})
.collect()
}
@@ -398,11 +396,6 @@ pub fn deserialize_diagnostics(
diagnostics
.into_iter()
.filter_map(|diagnostic| {
let data = if let Some(data) = diagnostic.data {
Some(Value::from_str(&data).ok()?)
} else {
None
};
Some(DiagnosticEntry {
range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?,
diagnostic: Diagnostic {
@@ -420,7 +413,6 @@ pub fn deserialize_diagnostics(
is_primary: diagnostic.is_primary,
is_disk_based: diagnostic.is_disk_based,
is_unnecessary: diagnostic.is_unnecessary,
data,
},
})
})

View File

@@ -2,40 +2,100 @@ use crate::LanguageModelRequest;
pub use anthropic::Model as AnthropicModel;
pub use ollama::Model as OllamaModel;
pub use open_ai::Model as OpenAiModel;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use strum::EnumIter;
use schemars::{
schema::{InstanceType, Metadata, Schema, SchemaObject},
JsonSchema,
};
use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::fmt;
use strum::{EnumIter, IntoEnumIterator};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
#[derive(Clone, Debug, Default, PartialEq, EnumIter)]
pub enum CloudModel {
#[serde(rename = "gpt-3.5-turbo")]
Gpt3Point5Turbo,
#[serde(rename = "gpt-4")]
Gpt4,
#[serde(rename = "gpt-4-turbo-preview")]
Gpt4Turbo,
#[serde(rename = "gpt-4o")]
#[default]
Gpt4Omni,
#[serde(rename = "gpt-4o-mini")]
Gpt4OmniMini,
#[serde(rename = "claude-3-5-sonnet")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-opus")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet")]
Claude3Sonnet,
#[serde(rename = "claude-3-haiku")]
Claude3Haiku,
#[serde(rename = "gemini-1.5-pro")]
Gemini15Pro,
#[serde(rename = "gemini-1.5-flash")]
Gemini15Flash,
#[serde(rename = "custom")]
Custom {
name: String,
max_tokens: Option<usize>,
},
Custom(String),
}
impl Serialize for CloudModel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.id())
}
}
impl<'de> Deserialize<'de> for CloudModel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ZedDotDevModelVisitor;
impl<'de> Visitor<'de> for ZedDotDevModelVisitor {
type Value = CloudModel;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string for a ZedDotDevModel variant or a custom model")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let model = CloudModel::iter()
.find(|model| model.id() == value)
.unwrap_or_else(|| CloudModel::Custom(value.to_string()));
Ok(model)
}
}
deserializer.deserialize_str(ZedDotDevModelVisitor)
}
}
impl JsonSchema for CloudModel {
fn schema_name() -> String {
"ZedDotDevModel".to_owned()
}
fn json_schema(_generator: &mut schemars::gen::SchemaGenerator) -> Schema {
let variants = CloudModel::iter()
.filter_map(|model| {
let id = model.id();
if id.is_empty() {
None
} else {
Some(id.to_string())
}
})
.collect::<Vec<_>>();
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(variants.iter().map(|s| s.clone().into()).collect()),
metadata: Some(Box::new(Metadata {
title: Some("ZedDotDevModel".to_owned()),
default: Some(CloudModel::default().id().into()),
examples: variants.into_iter().map(Into::into).collect(),
..Default::default()
})),
..Default::default()
})
}
}
impl CloudModel {
@@ -52,7 +112,7 @@ impl CloudModel {
Self::Claude3Haiku => "claude-3-haiku",
Self::Gemini15Pro => "gemini-1.5-pro",
Self::Gemini15Flash => "gemini-1.5-flash",
Self::Custom { name, .. } => name,
Self::Custom(id) => id,
}
}
@@ -69,7 +129,7 @@ impl CloudModel {
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Gemini15Pro => "Gemini 1.5 Pro",
Self::Gemini15Flash => "Gemini 1.5 Flash",
Self::Custom { name, .. } => name,
Self::Custom(id) => id.as_str(),
}
}
@@ -85,20 +145,14 @@ impl CloudModel {
| Self::Claude3Haiku => 200000,
Self::Gemini15Pro => 128000,
Self::Gemini15Flash => 32000,
Self::Custom { max_tokens, .. } => max_tokens.unwrap_or(200_000),
Self::Custom(_) => 4096, // TODO: Make this configurable
}
}
pub fn preprocess_request(&self, request: &mut LanguageModelRequest) {
match self {
Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku
| Self::Claude3_5Sonnet => {
request.preprocess_anthropic();
}
Self::Custom { name, .. } if name.starts_with("anthropic/") => {
request.preprocess_anthropic();
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => {
request.preprocess_anthropic()
}
_ => {}
}

View File

@@ -22,7 +22,7 @@ use util::ResultExt;
#[cfg(windows)]
use smol::process::windows::CommandExt;
const VERSION: &str = "v22.5.1";
const VERSION: &str = "v18.15.0";
#[cfg(not(windows))]
const NODE_PATH: &str = "bin/node";
@@ -269,16 +269,7 @@ impl NodeRuntime for RealNodeRuntime {
}
if let Some(proxy) = self.http.proxy() {
// Map proxy settings from `http://localhost:10809` to `http://127.0.0.1:10809`
// NodeRuntime without environment information can not parse `localhost`
// correctly.
// TODO: map to `[::1]` if we are using ipv6
let proxy = proxy
.to_string()
.to_ascii_lowercase()
.replace("localhost", "127.0.0.1");
command.args(["--proxy", &proxy]);
command.args(["--proxy", proxy]);
}
#[cfg(windows)]

View File

@@ -457,9 +457,7 @@ impl OutlinePanel {
}
fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
if self.filter_editor.focus_handle(cx).is_focused(cx) {
cx.propagate()
} else if let Some(selected_entry) = self.selected_entry.clone() {
if let Some(selected_entry) = self.selected_entry.clone() {
self.open_entry(&selected_entry, cx);
}
}

View File

@@ -4595,7 +4595,6 @@ impl Project {
is_primary: true,
is_disk_based,
is_unnecessary,
data: diagnostic.data.clone(),
},
});
if let Some(infos) = &diagnostic.related_information {
@@ -4613,7 +4612,6 @@ impl Project {
is_primary: false,
is_disk_based,
is_unnecessary: false,
data: diagnostic.data.clone(),
},
});
}

View File

@@ -1890,7 +1890,6 @@ message Diagnostic {
Information = 3;
Hint = 4;
}
optional string data = 12;
}
message Operation {

View File

@@ -85,7 +85,6 @@ pub trait SshClientDelegate {
platform: SshPlatform,
cx: &mut AsyncAppContext,
) -> oneshot::Receiver<Result<(PathBuf, SemanticVersion)>>;
fn set_status(&self, status: Option<&str>, cx: &mut AsyncAppContext);
}
type ResponseChannels = Mutex<HashMap<MessageId, oneshot::Sender<(Envelope, oneshot::Sender<()>)>>>;
@@ -100,18 +99,16 @@ impl SshSession {
) -> Result<Arc<Self>> {
let client_state = SshClientState::new(user, host, port, delegate.clone(), cx).await?;
let platform = client_state.query_platform().await?;
let platform = query_platform(&client_state).await?;
let (local_binary_path, version) = delegate.get_server_binary(platform, cx).await??;
let remote_binary_path = delegate.remote_server_binary_path(cx)?;
client_state
.ensure_server_binary(
&delegate,
&local_binary_path,
&remote_binary_path,
version,
cx,
)
.await?;
ensure_server_binary(
&client_state,
&local_binary_path,
&remote_binary_path,
version,
)
.await?;
let (spawn_process_tx, mut spawn_process_rx) = mpsc::unbounded::<SpawnRequest>();
let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded::<Envelope>();
@@ -416,7 +413,7 @@ impl SshClientState {
host: String,
port: u16,
delegate: Arc<dyn SshClientDelegate>,
cx: &mut AsyncAppContext,
cx: &AsyncAppContext,
) -> Result<Self> {
Err(anyhow!("ssh is not supported on this platform"))
}
@@ -427,29 +424,25 @@ impl SshClientState {
host: String,
port: u16,
delegate: Arc<dyn SshClientDelegate>,
cx: &mut AsyncAppContext,
cx: &AsyncAppContext,
) -> Result<Self> {
use futures::{io::BufReader, AsyncBufReadExt as _};
use smol::{fs::unix::PermissionsExt as _, net::unix::UnixListener};
use smol::fs::unix::PermissionsExt as _;
use util::ResultExt as _;
delegate.set_status(Some("connecting"), cx);
let url = format!("{user}@{host}");
let temp_dir = tempfile::Builder::new()
.prefix("zed-ssh-session")
.tempdir()?;
// Create a domain socket listener to handle requests from the askpass program.
let askpass_socket = temp_dir.path().join("askpass.sock");
let listener =
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
// Create a TCP listener to handle requests from the askpass program.
let listener = smol::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("failed to find open port");
let askpass_port = listener.local_addr().unwrap().port();
let askpass_task = cx.spawn(|mut cx| async move {
while let Ok((mut stream, _)) = listener.accept().await {
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
if stream.read_to_end(&mut buffer).await.is_err() {
buffer.clear();
}
let password_prompt = String::from_utf8_lossy(&buffer);
@@ -465,12 +458,10 @@ impl SshClientState {
}
});
// Create an askpass script that communicates back to this process.
// Create an askpass script that communicates back to this process using TCP.
let askpass_script = format!(
"{shebang}\n{print_args} | nc -U {askpass_socket} 2> /dev/null \n",
askpass_socket = askpass_socket.display(),
print_args = "printf '%s\\0' \"$@\"",
shebang = "#!/bin/sh",
"{shebang}\n echo \"$@\" | nc 127.0.0.1 {askpass_port} 2> /dev/null",
shebang = "#!/bin/sh"
);
let askpass_script_path = temp_dir.path().join("askpass.sh");
fs::write(&askpass_script_path, askpass_script).await?;
@@ -510,87 +501,14 @@ impl SshClientState {
}
Ok(Self {
url,
port,
socket_path,
_master_process: master_process,
port,
_temp_dir: temp_dir,
socket_path,
url,
})
}
async fn ensure_server_binary(
&self,
delegate: &Arc<dyn SshClientDelegate>,
src_path: &Path,
dst_path: &Path,
version: SemanticVersion,
cx: &mut AsyncAppContext,
) -> Result<()> {
let mut dst_path_gz = dst_path.to_path_buf();
dst_path_gz.set_extension("gz");
if let Some(parent) = dst_path.parent() {
run_cmd(self.ssh_command("mkdir").arg("-p").arg(parent)).await?;
}
let mut server_binary_exists = false;
if let Ok(installed_version) = run_cmd(self.ssh_command(&dst_path).arg("version")).await {
if installed_version.trim() == version.to_string() {
server_binary_exists = true;
}
}
if server_binary_exists {
log::info!("remote development server already present",);
return Ok(());
}
let src_stat = fs::metadata(src_path).await?;
let size = src_stat.len();
let server_mode = 0o755;
let t0 = Instant::now();
delegate.set_status(Some("uploading remote development server"), cx);
log::info!("uploading remote development server ({}kb)", size / 1024);
self.upload_file(src_path, &dst_path_gz)
.await
.context("failed to upload server binary")?;
log::info!("uploaded remote development server in {:?}", t0.elapsed());
delegate.set_status(Some("extracting remote development server"), cx);
run_cmd(self.ssh_command("gunzip").arg("--force").arg(&dst_path_gz)).await?;
delegate.set_status(Some("unzipping remote development server"), cx);
run_cmd(
self.ssh_command("chmod")
.arg(format!("{:o}", server_mode))
.arg(&dst_path),
)
.await?;
Ok(())
}
async fn query_platform(&self) -> Result<SshPlatform> {
let os = run_cmd(self.ssh_command("uname").arg("-s")).await?;
let arch = run_cmd(self.ssh_command("uname").arg("-m")).await?;
let os = match os.trim() {
"Darwin" => "macos",
"Linux" => "linux",
_ => Err(anyhow!("unknown uname os {os:?}"))?,
};
let arch = if arch.starts_with("arm") || arch.starts_with("aarch64") {
"aarch64"
} else if arch.starts_with("x86") || arch.starts_with("i686") {
"x86_64"
} else {
Err(anyhow!("unknown uname architecture {arch:?}"))?
};
Ok(SshPlatform { os, arch })
}
async fn upload_file(&self, src_path: &Path, dest_path: &Path) -> Result<()> {
let mut command = process::Command::new("scp");
let output = self
@@ -645,3 +563,81 @@ async fn run_cmd(command: &mut process::Command) -> Result<String> {
))
}
}
async fn query_platform(session: &SshClientState) -> Result<SshPlatform> {
let os = run_cmd(session.ssh_command("uname").arg("-s")).await?;
let arch = run_cmd(session.ssh_command("uname").arg("-m")).await?;
let os = match os.trim() {
"Darwin" => "macos",
"Linux" => "linux",
_ => Err(anyhow!("unknown uname os {os:?}"))?,
};
let arch = if arch.starts_with("arm") || arch.starts_with("aarch64") {
"aarch64"
} else if arch.starts_with("x86") || arch.starts_with("i686") {
"x86_64"
} else {
Err(anyhow!("unknown uname architecture {arch:?}"))?
};
Ok(SshPlatform { os, arch })
}
async fn ensure_server_binary(
session: &SshClientState,
src_path: &Path,
dst_path: &Path,
version: SemanticVersion,
) -> Result<()> {
let mut dst_path_gz = dst_path.to_path_buf();
dst_path_gz.set_extension("gz");
if let Some(parent) = dst_path.parent() {
run_cmd(session.ssh_command("mkdir").arg("-p").arg(parent)).await?;
}
let mut server_binary_exists = false;
if let Ok(installed_version) = run_cmd(session.ssh_command(&dst_path).arg("version")).await {
if installed_version.trim() == version.to_string() {
server_binary_exists = true;
}
}
if server_binary_exists {
log::info!("remote development server already present",);
return Ok(());
}
let src_stat = fs::metadata(src_path).await?;
let size = src_stat.len();
let server_mode = 0o755;
let t0 = Instant::now();
log::info!("uploading remote development server ({}kb)", size / 1024);
session
.upload_file(src_path, &dst_path_gz)
.await
.context("failed to upload server binary")?;
log::info!("uploaded remote development server in {:?}", t0.elapsed());
log::info!("extracting remote development server");
run_cmd(
session
.ssh_command("gunzip")
.arg("--force")
.arg(&dst_path_gz),
)
.await?;
log::info!("unzipping remote development server");
run_cmd(
session
.ssh_command("chmod")
.arg(format!("{:o}", server_mode))
.arg(&dst_path),
)
.await?;
Ok(())
}

View File

@@ -29,7 +29,7 @@ pub struct KernelSpecification {
impl KernelSpecification {
#[must_use]
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
fn command(&self, connection_path: &PathBuf) -> anyhow::Result<Command> {
let argv = &self.kernelspec.argv;
anyhow::ensure!(!argv.is_empty(), "Empty argv in kernelspec {}", self.name);
@@ -60,7 +60,7 @@ impl KernelSpecification {
// Find a set of open ports. This creates a listener with port set to 0. The listener will be closed at the end when it goes out of scope.
// There's a race condition between closing the ports and usage by a kernel, but it's inherent to the Jupyter protocol.
async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
async fn peek_ports(ip: IpAddr) -> anyhow::Result<[u16; 5]> {
let mut addr_zeroport: SocketAddr = SocketAddr::new(ip, 0);
addr_zeroport.set_port(0);
let mut ports: [u16; 5] = [0; 5];
@@ -166,10 +166,10 @@ impl Kernel {
pub struct RunningKernel {
pub process: smol::process::Child,
_shell_task: Task<Result<()>>,
_iopub_task: Task<Result<()>>,
_control_task: Task<Result<()>>,
_routing_task: Task<Result<()>>,
_shell_task: Task<anyhow::Result<()>>,
_iopub_task: Task<anyhow::Result<()>>,
_control_task: Task<anyhow::Result<()>>,
_routing_task: Task<anyhow::Result<()>>,
connection_path: PathBuf,
pub working_directory: PathBuf,
pub request_tx: mpsc::Sender<JupyterMessage>,
@@ -194,7 +194,7 @@ impl RunningKernel {
working_directory: PathBuf,
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) -> Task<Result<(Self, JupyterMessageChannel)>> {
) -> Task<anyhow::Result<(Self, JupyterMessageChannel)>> {
cx.spawn(|cx| async move {
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let ports = peek_ports(ip).await?;
@@ -332,7 +332,7 @@ async fn read_kernelspec_at(
// /usr/local/share/jupyter/kernels/python3
kernel_dir: PathBuf,
fs: &dyn Fs,
) -> Result<KernelSpecification> {
) -> anyhow::Result<KernelSpecification> {
let path = kernel_dir;
let kernel_name = if let Some(kernel_name) = path.file_name() {
kernel_name.to_string_lossy().to_string()
@@ -356,7 +356,7 @@ async fn read_kernelspec_at(
}
/// Read a directory of kernelspec directories
async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<KernelSpecification>> {
async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> anyhow::Result<Vec<KernelSpecification>> {
let mut kernelspec_dirs = fs.read_dir(&path).await?;
let mut valid_kernelspecs = Vec::new();
@@ -376,7 +376,7 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<KernelSpecif
Ok(valid_kernelspecs)
}
pub async fn kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<KernelSpecification>> {
pub async fn kernel_specifications(fs: Arc<dyn Fs>) -> anyhow::Result<Vec<KernelSpecification>> {
let data_dirs = dirs::data_dirs();
let kernel_dirs = data_dirs
.iter()

View File

@@ -281,7 +281,7 @@ impl ErrorView {
v_flex()
.w_full()
.bg(colors.background)
.py(cx.line_height() / 2.)
.p_4()
.border_l_1()
.border_color(theme.status().error_border)
.child(
@@ -297,7 +297,7 @@ impl ErrorView {
impl LineHeight for ErrorView {
fn num_lines(&self, cx: &mut WindowContext) -> u8 {
let mut height: u8 = 1; // Start at 1 to account for the y padding
let mut height: u8 = 0;
height = height.saturating_add(self.ename.lines().count() as u8);
height = height.saturating_add(self.evalue.lines().count() as u8);
height = height.saturating_add(self.traceback.num_lines(cx));
@@ -503,38 +503,29 @@ impl ExecutionView {
impl Render for ExecutionView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
if self.outputs.len() == 0 {
return v_flex()
.min_h(cx.line_height())
.justify_center()
.child(match &self.status {
ExecutionStatus::ConnectingToKernel => Label::new("Connecting to kernel...")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Executing => Label::new("Executing...")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Finished => Icon::new(IconName::Check)
.size(IconSize::Small)
.into_any_element(),
ExecutionStatus::Unknown => Label::new("Unknown status")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::ShuttingDown => Label::new("Kernel shutting down...")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Shutdown => Label::new("Kernel shutdown")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Queued => {
Label::new("Queued").color(Color::Muted).into_any_element()
}
ExecutionStatus::KernelErrored(error) => {
Label::new(format!("Kernel error: {}", error))
.color(Color::Error)
.into_any_element()
}
})
.into_any_element();
return match &self.status {
ExecutionStatus::ConnectingToKernel => {
div().child(Label::new("Connecting to kernel...").color(Color::Muted))
}
ExecutionStatus::Executing => {
div().child(Label::new("Executing...").color(Color::Muted))
}
ExecutionStatus::Finished => div().child(Icon::new(IconName::Check)),
ExecutionStatus::Unknown => {
div().child(div().child(Label::new("Unknown status").color(Color::Muted)))
}
ExecutionStatus::ShuttingDown => {
div().child(Label::new("Kernel shutting down...").color(Color::Muted))
}
ExecutionStatus::Shutdown => {
div().child(Label::new("Kernel shutdown").color(Color::Muted))
}
ExecutionStatus::Queued => div().child(Label::new("Queued").color(Color::Muted)),
ExecutionStatus::KernelErrored(error) => {
div().child(Label::new(format!("Kernel error: {}", error)).color(Color::Error))
}
}
.into_any_element();
}
div()
@@ -553,10 +544,9 @@ impl LineHeight for ExecutionView {
self.outputs
.iter()
.map(|output| output.num_lines(cx))
.fold(0_u8, |acc, additional_height| {
.fold(0, |acc, additional_height| {
acc.saturating_add(additional_height)
})
.max(1)
}
}

View File

@@ -1,13 +1,11 @@
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
use gpui::{AppContext, PlatformDispatcher};
use project::Fs;
use settings::Settings as _;
use std::{sync::Arc, time::Duration};
mod jupyter_settings;
mod kernels;
mod outputs;
mod repl_store;
mod runtime_panel;
mod session;
mod stdio;
@@ -19,8 +17,6 @@ pub use runtime_panel::{RuntimePanel, SessionSupport};
pub use runtimelib::ExecutionState;
pub use session::Session;
use crate::repl_store::ReplStore;
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
struct ZedDispatcher {
dispatcher: Arc<dyn PlatformDispatcher>,
@@ -45,10 +41,8 @@ fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
}
}
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
pub fn init(cx: &mut AppContext) {
set_dispatcher(zed_dispatcher(cx));
JupyterSettings::register(cx);
editor::init_settings(cx);
runtime_panel::init(cx);
ReplStore::init(fs, cx);
runtime_panel::init(cx)
}

View File

@@ -1,118 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use collections::HashMap;
use gpui::{
prelude::*, AppContext, EntityId, Global, Model, ModelContext, Subscription, Task, View,
};
use language::Language;
use project::Fs;
use settings::{Settings, SettingsStore};
use crate::kernels::kernel_specifications;
use crate::{JupyterSettings, KernelSpecification, Session};
struct GlobalReplStore(Model<ReplStore>);
impl Global for GlobalReplStore {}
pub struct ReplStore {
fs: Arc<dyn Fs>,
enabled: bool,
sessions: HashMap<EntityId, View<Session>>,
kernel_specifications: Vec<KernelSpecification>,
_subscriptions: Vec<Subscription>,
}
impl ReplStore {
pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let store = cx.new_model(move |cx| Self::new(fs, cx));
cx.set_global(GlobalReplStore(store))
}
pub fn global(cx: &AppContext) -> Model<Self> {
cx.global::<GlobalReplStore>().0.clone()
}
pub fn new(fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| {
this.set_enabled(JupyterSettings::enabled(cx), cx);
})];
Self {
fs,
enabled: JupyterSettings::enabled(cx),
sessions: HashMap::default(),
kernel_specifications: Vec::new(),
_subscriptions: subscriptions,
}
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn kernel_specifications(&self) -> impl Iterator<Item = &KernelSpecification> {
self.kernel_specifications.iter()
}
pub fn sessions(&self) -> impl Iterator<Item = &View<Session>> {
self.sessions.values()
}
fn set_enabled(&mut self, enabled: bool, cx: &mut ModelContext<Self>) {
if self.enabled != enabled {
self.enabled = enabled;
cx.notify();
}
}
pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let kernel_specifications = kernel_specifications(self.fs.clone());
cx.spawn(|this, mut cx| async move {
let kernel_specifications = kernel_specifications.await?;
this.update(&mut cx, |this, cx| {
this.kernel_specifications = kernel_specifications;
cx.notify();
})
})
}
pub fn kernelspec(
&self,
language: &Language,
cx: &mut ModelContext<Self>,
) -> Option<KernelSpecification> {
let settings = JupyterSettings::get_global(cx);
let language_name = language.code_fence_block_name();
let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
self.kernel_specifications
.iter()
.find(|runtime_specification| {
if let Some(selected) = selected_kernel {
// Top priority is the selected kernel
runtime_specification.name.to_lowercase() == selected.to_lowercase()
} else {
// Otherwise, we'll try to find a kernel that matches the language
runtime_specification.kernelspec.language.to_lowercase()
== language_name.to_lowercase()
}
})
.cloned()
}
pub fn get_session(&self, entity_id: EntityId) -> Option<&View<Session>> {
self.sessions.get(&entity_id)
}
pub fn insert_session(&mut self, entity_id: EntityId, session: View<Session>) {
self.sessions.insert(entity_id, session);
}
pub fn remove_session(&mut self, entity_id: EntityId) {
self.sessions.remove(&entity_id);
}
}

View File

@@ -1,19 +1,19 @@
use crate::repl_store::ReplStore;
use crate::{
jupyter_settings::{JupyterDockPosition, JupyterSettings},
kernels::KernelSpecification,
kernels::{kernel_specifications, KernelSpecification},
session::{Session, SessionEvent},
};
use anyhow::{Context as _, Result};
use collections::HashMap;
use editor::{Anchor, Editor, RangeToAnchorExt};
use gpui::{
actions, prelude::*, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, Subscription, Task, View, WeakView,
actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, Subscription, Task, View, WeakView,
};
use language::{Language, Point};
use multi_buffer::MultiBufferRow;
use project::Fs;
use settings::Settings as _;
use settings::{Settings as _, SettingsStore};
use std::{ops::Range, sync::Arc};
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
use util::ResultExt as _;
@@ -28,13 +28,6 @@ actions!(
);
actions!(repl_panel, [ToggleFocus]);
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(Box<KernelSpecification>),
RequiresSetup(Arc<str>),
Unsupported,
}
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
@@ -42,11 +35,12 @@ pub fn init(cx: &mut AppContext) {
workspace.toggle_panel_focus::<RuntimePanel>(cx);
});
workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
let store = ReplStore::global(cx);
store.update(cx, |store, cx| {
store.refresh_kernelspecs(cx).detach();
});
workspace.register_action(|workspace, _: &RefreshKernelspecs, cx| {
if let Some(panel) = workspace.panel::<RuntimePanel>(cx) {
panel.update(cx, |panel, cx| {
panel.refresh_kernelspecs(cx).detach();
});
}
});
},
)
@@ -151,8 +145,11 @@ pub fn init(cx: &mut AppContext) {
pub struct RuntimePanel {
fs: Arc<dyn Fs>,
enabled: bool,
focus_handle: FocusHandle,
width: Option<Pixels>,
sessions: HashMap<EntityId, View<Session>>,
kernel_specifications: Vec<KernelSpecification>,
_subscriptions: Vec<Subscription>,
}
@@ -171,29 +168,39 @@ impl RuntimePanel {
let subscriptions = vec![
cx.on_focus_in(&focus_handle, Self::focus_in),
cx.on_focus_out(&focus_handle, Self::focus_out),
cx.observe_global::<SettingsStore>(move |this, cx| {
this.set_enabled(JupyterSettings::enabled(cx), cx);
}),
];
let runtime_panel = Self {
fs,
fs: fs.clone(),
width: None,
focus_handle,
kernel_specifications: Vec::new(),
sessions: Default::default(),
_subscriptions: subscriptions,
enabled: JupyterSettings::enabled(cx),
};
runtime_panel
})
})?;
view.update(&mut cx, |_panel, cx| {
let store = ReplStore::global(cx);
store.update(cx, |store, cx| store.refresh_kernelspecs(cx))
})?
.await?;
view.update(&mut cx, |this, cx| this.refresh_kernelspecs(cx))?
.await?;
Ok(view)
})
}
fn set_enabled(&mut self, enabled: bool, cx: &mut ViewContext<Self>) {
if self.enabled != enabled {
self.enabled = enabled;
cx.notify();
}
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
cx.notify();
}
@@ -202,7 +209,8 @@ impl RuntimePanel {
cx.notify();
}
fn snippet(
pub fn snippet(
&self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
@@ -247,59 +255,93 @@ impl RuntimePanel {
Some((selected_text, start_language.clone(), anchor_range))
}
fn language(editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Option<Arc<Language>> {
pub fn language(
&self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> Option<Arc<Language>> {
let editor = editor.upgrade()?;
let selection = editor.read(cx).selections.newest::<usize>(cx);
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
buffer.language_at(selection.head()).cloned()
}
pub fn run(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Result<()> {
let store = ReplStore::global(cx);
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
let kernel_specifications = kernel_specifications(self.fs.clone());
cx.spawn(|this, mut cx| async move {
let kernel_specifications = kernel_specifications.await?;
if !store.read(cx).is_enabled() {
this.update(&mut cx, |this, cx| {
this.kernel_specifications = kernel_specifications;
cx.notify();
})
})
}
pub fn kernelspec(
&self,
language: &Language,
cx: &mut ViewContext<Self>,
) -> Option<KernelSpecification> {
let settings = JupyterSettings::get_global(cx);
let language_name = language.code_fence_block_name();
let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
self.kernel_specifications
.iter()
.find(|runtime_specification| {
if let Some(selected) = selected_kernel {
// Top priority is the selected kernel
runtime_specification.name.to_lowercase() == selected.to_lowercase()
} else {
// Otherwise, we'll try to find a kernel that matches the language
runtime_specification.kernelspec.language.to_lowercase()
== language_name.to_lowercase()
}
})
.cloned()
}
pub fn run(
&mut self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> anyhow::Result<()> {
if !self.enabled {
return Ok(());
}
let (selected_text, language, anchor_range) = match Self::snippet(editor.clone(), cx) {
let (selected_text, language, anchor_range) = match self.snippet(editor.clone(), cx) {
Some(snippet) => snippet,
None => return Ok(()),
};
let entity_id = editor.entity_id();
let kernel_specification = store.update(cx, |store, cx| {
store
.kernelspec(&language, cx)
.with_context(|| format!("No kernel found for language: {}", language.name()))
})?;
let kernel_specification = self
.kernelspec(&language, cx)
.with_context(|| format!("No kernel found for language: {}", language.name()))?;
let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
session
} else {
let session =
let session = self.sessions.entry(entity_id).or_insert_with(|| {
let view =
cx.new_view(|cx| Session::new(editor, self.fs.clone(), kernel_specification, cx));
cx.notify();
let subscription = cx.subscribe(&session, {
let store = store.clone();
move |_this, _session, event, cx| match event {
SessionEvent::Shutdown(shutdown_event) => {
store.update(cx, |store, _cx| {
store.remove_session(shutdown_event.entity_id());
});
let subscription = cx.subscribe(
&view,
|panel: &mut RuntimePanel, _session: View<Session>, event: &SessionEvent, _cx| {
match event {
SessionEvent::Shutdown(shutdown_event) => {
panel.sessions.remove(&shutdown_event.entity_id());
}
}
}
});
},
);
subscription.detach();
store.update(cx, |store, _cx| {
store.insert_session(entity_id, session.clone());
});
session
};
view
});
session.update(cx, |session, cx| {
session.execute(&selected_text, anchor_range, cx);
@@ -308,38 +350,9 @@ impl RuntimePanel {
anyhow::Ok(())
}
pub fn session(
&mut self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> SessionSupport {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
return SessionSupport::ActiveSession(session);
};
let language = Self::language(editor, cx);
let language = match language {
Some(language) => language,
None => return SessionSupport::Unsupported,
};
let kernelspec = store.update(cx, |store, cx| store.kernelspec(&language, cx));
match kernelspec {
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
None => match language.name().as_ref() {
"TypeScript" | "Python" => SessionSupport::RequiresSetup(language.name()),
_ => SessionSupport::Unsupported,
},
}
}
pub fn clear_outputs(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
if let Some(session) = self.sessions.get_mut(&entity_id) {
session.update(cx, |session, cx| {
session.clear_outputs(cx);
});
@@ -348,9 +361,8 @@ impl RuntimePanel {
}
pub fn interrupt(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
if let Some(session) = self.sessions.get_mut(&entity_id) {
session.update(cx, |session, cx| {
session.interrupt(cx);
});
@@ -358,10 +370,9 @@ impl RuntimePanel {
}
}
pub fn shutdown(&self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
let store = ReplStore::global(cx);
pub fn shutdown(&self, editor: WeakView<Editor>, cx: &mut ViewContext<RuntimePanel>) {
let entity_id = editor.entity_id();
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
if let Some(session) = self.sessions.get(&entity_id) {
session.update(cx, |session, cx| {
session.shutdown(cx);
});
@@ -370,6 +381,51 @@ impl RuntimePanel {
}
}
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(Box<KernelSpecification>),
RequiresSetup(Arc<str>),
Unsupported,
}
impl RuntimePanel {
pub fn session(
&mut self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> SessionSupport {
let entity_id = editor.entity_id();
let session = self.sessions.get(&entity_id).cloned();
match session {
Some(session) => SessionSupport::ActiveSession(session),
None => {
let language = self.language(editor, cx);
let language = match language {
Some(language) => language,
None => return SessionSupport::Unsupported,
};
// Check for kernelspec
let kernelspec = self.kernelspec(&language, cx);
match kernelspec {
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
None => {
// If no kernelspec but language is one of typescript or python
// then we return RequiresSetup
match language.name().as_ref() {
"TypeScript" | "Python" => {
SessionSupport::RequiresSetup(language.name())
}
_ => SessionSupport::Unsupported,
}
}
}
}
}
}
}
impl Panel for RuntimePanel {
fn persistent_name() -> &'static str {
"RuntimePanel"
@@ -412,10 +468,8 @@ impl Panel for RuntimePanel {
self.width = size;
}
fn icon(&self, cx: &ui::WindowContext) -> Option<ui::IconName> {
let store = ReplStore::global(cx);
if !store.read(cx).is_enabled() {
fn icon(&self, _cx: &ui::WindowContext) -> Option<ui::IconName> {
if !self.enabled {
return None;
}
@@ -441,47 +495,38 @@ impl FocusableView for RuntimePanel {
impl Render for RuntimePanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let store = ReplStore::global(cx);
let (kernel_specifications, sessions) = store.update(cx, |store, _cx| {
(
store.kernel_specifications().cloned().collect::<Vec<_>>(),
store.sessions().cloned().collect::<Vec<_>>(),
)
});
// When there are no kernel specifications, show a link to the Zed docs explaining how to
// install kernels. It can be assumed they don't have a running kernel if we have no
// specifications.
if kernel_specifications.is_empty() {
if self.kernel_specifications.is_empty() {
return v_flex()
.p_4()
.size_full()
.gap_2()
.child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
.child(
Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
.size(LabelSize::Default),
)
.child(
h_flex().w_full().p_4().justify_center().gap_2().child(
ButtonLike::new("install-kernels")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Install Kernels"))
.on_click(move |_, cx| {
cx.open_url(
"https://docs.jupyter.org/en/latest/install/kernels.html",
)
}),
),
)
.child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
.child(
Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
.size(LabelSize::Default),
)
.child(
h_flex().w_full().p_4().justify_center().gap_2().child(
ButtonLike::new("install-kernels")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Install Kernels"))
.on_click(move |_, cx| {
cx.open_url(
"https://docs.jupyter.org/en/latest/install/kernels.html",
)
}),
),
)
.into_any_element();
}
// When there are no sessions, show the command to run code in an editor
if sessions.is_empty() {
if self.sessions.is_empty() {
return v_flex()
.p_4()
.size_full()
@@ -501,7 +546,7 @@ impl Render for RuntimePanel {
)
.child(Label::new("Kernels available").size(LabelSize::Large))
.children(
kernel_specifications.into_iter().map(|spec| {
self.kernel_specifications.iter().map(|spec| {
h_flex().gap_2().child(Label::new(spec.name.clone()))
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
})
@@ -514,8 +559,8 @@ impl Render for RuntimePanel {
.p_4()
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
.children(
sessions
.into_iter()
self.sessions
.values()
.map(|session| session.clone().into_any_element()),
)
.into_any_element()

View File

@@ -5,16 +5,14 @@ use crate::{
use collections::{HashMap, HashSet};
use editor::{
display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId,
RenderBlock,
BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
},
scroll::Autoscroll,
Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint,
};
use futures::{FutureExt as _, StreamExt as _};
use gpui::{
div, prelude::*, EntityId, EventEmitter, Model, Render, Subscription, Task, View, ViewContext,
WeakView,
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView,
};
use language::Point;
use project::Fs;
@@ -24,7 +22,7 @@ use runtimelib::{
use settings::Settings as _;
use std::{env::temp_dir, ops::Range, path::PathBuf, sync::Arc, time::Duration};
use theme::{ActiveTheme, ThemeSettings};
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, IconButtonShape, Label, Tooltip};
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
pub struct Session {
pub editor: WeakView<Editor>,
@@ -41,18 +39,13 @@ struct EditorBlock {
invalidation_anchor: Anchor,
block_id: CustomBlockId,
execution_view: View<ExecutionView>,
on_close: CloseBlockFn,
}
type CloseBlockFn =
Arc<dyn for<'a> Fn(CustomBlockId, &'a mut WindowContext) + Send + Sync + 'static>;
impl EditorBlock {
fn new(
editor: WeakView<Editor>,
code_range: Range<Anchor>,
status: ExecutionStatus,
on_close: CloseBlockFn,
cx: &mut ViewContext<Session>,
) -> anyhow::Result<Self> {
let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx));
@@ -80,7 +73,7 @@ impl EditorBlock {
position: code_range.end,
height: execution_view.num_lines(cx).saturating_add(1),
style: BlockStyle::Sticky,
render: Self::create_output_area_render(execution_view.clone(), on_close.clone()),
render: Self::create_output_area_render(execution_view.clone()),
disposition: BlockDisposition::Below,
};
@@ -94,7 +87,6 @@ impl EditorBlock {
invalidation_anchor,
block_id,
execution_view,
on_close,
})
}
@@ -106,15 +98,11 @@ impl EditorBlock {
self.editor
.update(cx, |editor, cx| {
let mut replacements = HashMap::default();
replacements.insert(
self.block_id,
(
Some(self.execution_view.num_lines(cx).saturating_add(1)),
Self::create_output_area_render(
self.execution_view.clone(),
self.on_close.clone(),
),
Self::create_output_area_render(self.execution_view.clone()),
),
);
editor.replace_blocks(replacements, None, cx);
@@ -122,74 +110,31 @@ impl EditorBlock {
.ok();
}
fn create_output_area_render(
execution_view: View<ExecutionView>,
on_close: CloseBlockFn,
) -> RenderBlock {
fn create_output_area_render(execution_view: View<ExecutionView>) -> RenderBlock {
let render = move |cx: &mut BlockContext| {
let execution_view = execution_view.clone();
let text_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
let text_font_size = ThemeSettings::get_global(cx).buffer_font_size;
// Note: we'll want to use `cx.anchor_x` when someone runs something with no output -- just show a checkmark and not make the full block below the line
let gutter = cx.gutter_dimensions;
let close_button_size = IconSize::XSmall;
let gutter_width = cx.gutter_dimensions.width;
let block_id = cx.block_id;
let on_close = on_close.clone();
let rem_size = cx.rem_size();
let line_height = cx.text_style().line_height_in_pixels(rem_size);
let (close_button_width, close_button_padding) =
close_button_size.square_components(cx);
div()
.min_h(line_height)
.flex()
.flex_row()
.items_start()
h_flex()
.w_full()
.bg(cx.theme().colors().background)
.border_y_1()
.border_color(cx.theme().colors().border)
.child(
v_flex().min_h(cx.line_height()).justify_center().child(
h_flex()
.w(gutter.full_width())
.justify_end()
.pt(line_height / 2.)
.child(
h_flex()
.pr(gutter.width / 2. - close_button_width
+ close_button_padding / 2.)
.child(
IconButton::new(
("close_output_area", EntityId::from(cx.block_id)),
IconName::Close,
)
.shape(IconButtonShape::Square)
.icon_size(close_button_size)
.icon_color(Color::Muted)
.tooltip(|cx| Tooltip::text("Close output area", cx))
.on_click(
move |_, cx| {
if let BlockId::Custom(block_id) = block_id {
(on_close)(block_id, cx)
}
},
),
),
),
),
)
.pl(gutter_width)
.child(
div()
.flex_1()
.size_full()
.my_2()
.mr(gutter.width)
.text_size(text_font_size)
.font_family(text_font)
// .ml(gutter_width)
.mx_1()
.my_2()
.h_full()
.w_full()
.mr(gutter_width)
.child(execution_view),
)
.into_any_element()
@@ -428,30 +373,8 @@ impl Session {
Kernel::Shutdown => ExecutionStatus::Shutdown,
};
let parent_message_id = message.header.msg_id.clone();
let session_view = cx.view().downgrade();
let weak_editor = self.editor.clone();
let on_close: CloseBlockFn =
Arc::new(move |block_id: CustomBlockId, cx: &mut WindowContext| {
if let Some(session) = session_view.upgrade() {
session.update(cx, |session, cx| {
session.blocks.remove(&parent_message_id);
cx.notify();
});
}
if let Some(editor) = weak_editor.upgrade() {
editor.update(cx, |editor, cx| {
let mut block_ids = HashSet::default();
block_ids.insert(block_id);
editor.remove_blocks(block_ids, None, cx);
});
}
});
let editor_block = if let Ok(editor_block) =
EditorBlock::new(self.editor.clone(), anchor_range, status, on_close, cx)
EditorBlock::new(self.editor.clone(), anchor_range, status, cx)
{
editor_block
} else {

View File

@@ -78,14 +78,7 @@ impl TerminalOutput {
})
.collect::<Vec<TextRun>>();
// Trim the last trailing newline for visual appeal
let trimmed = self
.handler
.buffer
.strip_suffix('\n')
.unwrap_or(&self.handler.buffer);
let text = StyledText::new(trimmed.to_string()).with_runs(runs);
let text = StyledText::new(self.handler.buffer.trim_end().to_string()).with_runs(runs);
div()
.font_family(buffer_font)
.child(text)
@@ -95,7 +88,7 @@ impl TerminalOutput {
impl LineHeight for TerminalOutput {
fn num_lines(&self, _cx: &mut WindowContext) -> u8 {
self.handler.buffer.lines().count().max(1) as u8
self.handler.buffer.lines().count() as u8
}
}
@@ -219,7 +212,7 @@ impl Perform for TerminalHandler {
}
_ => {
// Format as hex
// println!("[execute] byte={:02x}", byte);
println!("[execute] byte={:02x}", byte);
}
}
}

View File

@@ -13,9 +13,10 @@ use editor::{
};
use futures::channel::oneshot;
use gpui::{
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView, Hsla,
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _, WindowContext,
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
FontStyle, Hsla, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render,
ScrollHandle, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _,
WhiteSpace, WindowContext,
};
use project::{
search::SearchQuery,
@@ -116,8 +117,12 @@ impl BufferSearchBar {
font_features: settings.buffer_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(

View File

@@ -13,9 +13,10 @@ use editor::{
};
use gpui::{
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, EntityId, EventEmitter,
FocusHandle, FocusableView, Global, Hsla, InteractiveElement, IntoElement, KeyContext, Model,
ModelContext, ParentElement, Point, Render, SharedString, Styled, Subscription, Task,
TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel, WindowContext,
FocusHandle, FocusableView, FontStyle, Global, Hsla, InteractiveElement, IntoElement,
KeyContext, Model, ModelContext, ParentElement, Point, Render, SharedString, Styled,
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel,
WhiteSpace, WindowContext,
};
use menu::Confirm;
use project::{search::SearchQuery, search_history::SearchHistoryCursor, Project, ProjectPath};
@@ -1340,8 +1341,12 @@ impl ProjectSearchBar {
font_features: settings.buffer_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
..Default::default()
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(

View File

@@ -47,20 +47,10 @@ fn parse_snippet<'a>(
source = parse_tabstop(&source[1..], text, tabstops)?;
}
Some('\\') => {
// As specified in the LSP spec (`Grammar` section),
// backslashes can escape some characters:
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax
source = &source[1..];
if let Some(c) = source.chars().next() {
if c == '$' || c == '\\' || c == '}' {
text.push(c);
// All escapable characters are 1 byte long:
source = &source[1..];
} else {
text.push('\\');
}
} else {
text.push('\\');
text.push(c);
source = &source[c.len_utf8()..];
}
}
Some('}') => {
@@ -207,17 +197,6 @@ mod tests {
let snippet = Snippet::parse("{a\\}").unwrap();
assert_eq!(snippet.text, "{a}");
assert_eq!(tabstops(&snippet), &[vec![3..3]]);
// backslash not functioning as an escape
let snippet = Snippet::parse("a\\b").unwrap();
assert_eq!(snippet.text, "a\\b");
assert_eq!(tabstops(&snippet), &[vec![3..3]]);
// first backslash cancelling escaping that would
// have happened with second backslash
let snippet = Snippet::parse("one\\\\$1two").unwrap();
assert_eq!(snippet.text, "one\\two");
assert_eq!(tabstops(&snippet), &[vec![4..4], vec![7..7]]);
}
fn tabstops(snippet: &Snippet) -> Vec<Vec<Range<isize>>> {

View File

@@ -14,7 +14,6 @@ pub enum OutboundMessage {
StateUpdate(StateUpdateMessage),
#[allow(dead_code)]
UseFreeVersion,
Logout,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@@ -9,9 +9,7 @@ use client::{proto, Client};
use collections::BTreeMap;
use futures::{channel::mpsc, io::BufReader, AsyncBufReadExt, StreamExt};
use gpui::{
actions, AppContext, AsyncAppContext, EntityId, Global, Model, ModelContext, Task, WeakModel,
};
use gpui::{AppContext, AsyncAppContext, EntityId, Global, Model, ModelContext, Task, WeakModel};
use language::{
language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot, ToOffset,
};
@@ -27,8 +25,6 @@ use std::{path::PathBuf, process::Stdio, sync::Arc};
use ui::prelude::*;
use util::ResultExt;
actions!(supermaven, [SignOut]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let supermaven = cx.new_model(|_| Supermaven::Starting);
Supermaven::set_global(supermaven.clone(), cx);
@@ -50,12 +46,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
}
})
.detach();
cx.on_action(|_: &SignOut, cx| {
if let Some(supermaven) = Supermaven::global(cx) {
supermaven.update(cx, |supermaven, _cx| supermaven.sign_out());
}
});
}
pub enum Supermaven {
@@ -185,19 +175,6 @@ impl Supermaven {
None
}
}
pub fn sign_out(&mut self) {
if let Self::Spawned(agent) = self {
agent
.outgoing_tx
.unbounded_send(OutboundMessage::Logout)
.ok();
// The account status will get set to RequiresActivation or Ready when the next
// message from the agent comes in. Until that happens, set the status to Unknown
// to disable the button.
agent.account_status = AccountStatus::Unknown;
}
}
}
fn find_relevant_completion<'a>(

View File

@@ -3,7 +3,7 @@ pub mod terminal_element;
pub mod terminal_panel;
use collections::HashSet;
use editor::{actions::SelectAll, scroll::Autoscroll, Editor};
use editor::{scroll::Autoscroll, Editor};
use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
anchored, deferred, div, impl_actions, AnyElement, AppContext, DismissEvent, EventEmitter,
@@ -33,8 +33,8 @@ use workspace::{
notifications::NotifyResultExt,
register_serializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, Pane, ToolbarItemLocation,
Workspace, WorkspaceId,
CloseActiveItem, NewCenterTerminal, OpenVisible, Pane, ToolbarItemLocation, Workspace,
WorkspaceId,
};
use anyhow::Context;
@@ -214,13 +214,7 @@ impl TerminalView {
cx: &mut ViewContext<Self>,
) {
let context_menu = ContextMenu::build(cx, |menu, _| {
menu.action("New Terminal", Box::new(NewTerminal))
.separator()
.action("Copy", Box::new(Copy))
.action("Paste", Box::new(Paste))
.action("Select All", Box::new(SelectAll))
.action("Clear", Box::new(Clear))
.separator()
menu.action("Clear", Box::new(Clear))
.action("Close", Box::new(CloseActiveItem { save_intent: None }))
});
@@ -264,7 +258,7 @@ impl TerminalView {
}
}
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
fn select_all(&mut self, _: &editor::actions::SelectAll, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| term.select_all());
cx.notify();
}

View File

@@ -106,7 +106,7 @@ fn main() -> Result<()> {
TermLogger::init(
LevelFilter::Trace,
log_config,
TerminalMode::Stderr,
TerminalMode::Mixed,
ColorChoice::Auto,
)
.expect("could not initialize logger");

View File

@@ -76,12 +76,8 @@ impl IconSize {
}
}
/// Returns the individual components of the square that contains this [`IconSize`].
///
/// The returned tuple contains:
/// 1. The length of one side of the square
/// 2. The padding of one side of the square
pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
/// Returns the length of a side of the square that contains this [`IconSize`], with padding.
pub(crate) fn square(&self, cx: &mut WindowContext) -> Pixels {
let icon_size = self.rems() * cx.rem_size();
let padding = match self {
IconSize::Indicator => Spacing::None.px(cx),
@@ -90,13 +86,6 @@ impl IconSize {
IconSize::Medium => Spacing::XSmall.px(cx),
};
(icon_size, padding)
}
/// Returns the length of a side of the square that contains this [`IconSize`], with padding.
pub fn square(&self, cx: &mut WindowContext) -> Pixels {
let (icon_size, padding) = self.square_components(cx);
icon_size + padding * 2.
}
}

View File

@@ -79,7 +79,7 @@ impl SettingStory {
)
.add_setting(
SettingsItem::new(
"font-weight",
"font-weifht",
"Font Weight".into(),
SettingType::Dropdown,
Some("400".to_string().into()),

View File

@@ -117,21 +117,6 @@ impl RenderOnce for Tab {
),
};
let (start_slot, end_slot) = {
let start_slot = h_flex().size_3().justify_center().children(self.start_slot);
let end_slot = h_flex()
.size_3()
.justify_center()
.visible_on_hover("")
.children(self.end_slot);
match self.close_side {
TabCloseSide::End => (start_slot, end_slot),
TabCloseSide::Start => (end_slot, start_slot),
}
};
self.div
.h(rems(Self::CONTAINER_HEIGHT_IN_REMS))
.bg(tab_bg)
@@ -161,12 +146,35 @@ impl RenderOnce for Tab {
.group("")
.relative()
.h(rems(Self::CONTENT_HEIGHT_IN_REMS))
.px(crate::custom_spacing(cx, 4.))
.px(crate::custom_spacing(cx, 20.))
.gap(Spacing::Small.rems(cx))
.text_color(text_color)
.child(start_slot)
.children(self.children)
.child(end_slot),
// .hover(|style| style.bg(tab_hover_bg))
// .active(|style| style.bg(tab_active_bg))
.child(
h_flex()
.size_3()
.justify_center()
.absolute()
.map(|this| match self.close_side {
TabCloseSide::Start => this.right(Spacing::Small.rems(cx)),
TabCloseSide::End => this.left(Spacing::Small.rems(cx)),
})
.children(self.start_slot),
)
.child(
h_flex()
.size_3()
.justify_center()
.absolute()
.map(|this| match self.close_side {
TabCloseSide::Start => this.left(Spacing::Small.rems(cx)),
TabCloseSide::End => this.right(Spacing::Small.rems(cx)),
})
.visible_on_hover("")
.children(self.end_slot),
)
.children(self.children),
)
}
}

View File

@@ -125,9 +125,7 @@ where
{
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
vim.store_visual_marks(cx);
let count = vim.take_count(cx).unwrap_or(1) as u32;
vim.update_active_editor(cx, |vim, editor, cx| {
let mut ranges = Vec::new();
let mut cursor_positions = Vec::new();

View File

@@ -44,7 +44,6 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
}
fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
vim.store_visual_marks(cx);
vim.update_active_editor(cx, |vim, editor, cx| {
let mut edits = Vec::new();
let mut new_anchors = Vec::new();

View File

@@ -30,9 +30,7 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>
fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
vim.store_visual_marks(cx);
let count = vim.take_count(cx).unwrap_or(1);
vim.update_active_editor(cx, |vim, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
editor.transact(cx, |editor, cx| {

View File

@@ -155,7 +155,6 @@ fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewCont
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
let mut motion = None;
Vim::update(cx, |vim, cx| {
vim.store_visual_marks(cx);
let pane = workspace.active_pane().clone();
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {

View File

@@ -29,7 +29,6 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>
}
pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
vim.store_visual_marks(cx);
vim.update_active_editor(cx, |vim, editor, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.transact(cx, |editor, cx| {

View File

@@ -391,15 +391,6 @@ impl Vim {
self.stop_recording();
}
// When handling an action, you must create visual marks if you will switch to normal
// mode without the default selection behaviour.
fn store_visual_marks(&mut self, cx: &mut WindowContext) {
let mode = self.state().mode;
if mode.is_visual() {
create_visual_marks(self, mode, cx);
}
}
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
let state = self.state();
let last_mode = state.mode;
@@ -422,14 +413,14 @@ impl Vim {
// Sync editor settings like clip mode
self.sync_vim_settings(cx);
if leave_selections {
return;
}
if !mode.is_visual() && last_mode.is_visual() {
create_visual_marks(self, last_mode, cx);
}
if leave_selections {
return;
}
// Adjust selections
self.update_active_editor(cx, |_, editor, cx| {
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock

View File

@@ -410,7 +410,6 @@ pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace
}
pub fn delete(vim: &mut Vim, line_mode: bool, cx: &mut WindowContext) {
vim.store_visual_marks(cx);
vim.update_active_editor(cx, |vim, editor, cx| {
let mut original_columns: HashMap<_, _> = Default::default();
let line_mode = line_mode || editor.selections.line_mode;
@@ -464,7 +463,6 @@ pub fn delete(vim: &mut Vim, line_mode: bool, cx: &mut WindowContext) {
}
pub fn yank(vim: &mut Vim, cx: &mut WindowContext) {
vim.store_visual_marks(cx);
vim.update_active_editor(cx, |vim, editor, cx| {
let line_mode = editor.selections.line_mode;
yank_selections_content(vim, editor, line_mode, cx);
@@ -1359,15 +1357,5 @@ mod test {
"},
Mode::Visual,
);
cx.simulate_keystrokes("y g v");
cx.assert_state(
indoc! {"
«fishˇ» one
«fishˇ» two
«fishˇ» red
«fishˇ» blue
"},
Mode::Visual,
);
}
}

View File

@@ -167,7 +167,7 @@ fn init_common(app_state: Arc<AppState>, cx: &mut AppContext) {
supermaven::init(app_state.client.clone(), cx);
inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
repl::init(app_state.fs.clone(), cx);
repl::init(cx);
extension::init(
app_state.fs.clone(),
app_state.client.clone(),

View File

@@ -5,7 +5,7 @@ pub(crate) mod linux_prompts;
#[cfg(not(target_os = "linux"))]
pub(crate) mod only_instance;
mod open_listener;
mod ssh_connection_modal;
mod password_prompt;
pub use app_menus::*;
use breadcrumbs::Breadcrumbs;
@@ -3417,7 +3417,7 @@ mod tests {
outline_panel::init((), cx);
terminal_view::init(cx);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
repl::init(app_state.fs.clone(), cx);
repl::init(cx);
tasks_ui::init(cx);
initialize_workspace(app_state.clone(), cx);
app_state

View File

@@ -1,6 +1,4 @@
use crate::{
handle_open_request, init_headless, init_ui, zed::ssh_connection_modal::SshConnectionModal,
};
use crate::{handle_open_request, init_headless, init_ui, zed::password_prompt::PasswordPrompt};
use anyhow::{anyhow, Context, Result};
use auto_update::AutoUpdater;
use cli::{ipc, IpcHandshake};
@@ -14,7 +12,7 @@ use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::channel::{mpsc, oneshot};
use futures::{FutureExt, SinkExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Global, SemanticVersion, View, VisualContext as _, WindowHandle,
AppContext, AsyncAppContext, Global, SemanticVersion, VisualContext as _, WindowHandle,
};
use language::{Bias, Point};
use release_channel::{AppVersion, ReleaseChannel};
@@ -157,10 +155,8 @@ impl OpenListener {
}
}
#[derive(Clone)]
struct SshClientDelegate {
window: WindowHandle<Workspace>,
modal: View<SshConnectionModal>,
known_password: Option<String>,
}
@@ -172,34 +168,27 @@ impl remote::SshClientDelegate for SshClientDelegate {
) -> oneshot::Receiver<Result<String>> {
let (tx, rx) = oneshot::channel();
let mut known_password = self.known_password.clone();
if let Some(password) = known_password.take() {
tx.send(Ok(password)).ok();
} else {
self.window
.update(cx, |_, cx| {
self.modal.update(cx, |modal, cx| {
modal.set_prompt(prompt, tx, cx);
});
})
.ok();
}
self.window
.update(cx, |workspace, cx| {
cx.activate_window();
if let Some(password) = known_password.take() {
tx.send(Ok(password)).ok();
} else {
workspace.toggle_modal(cx, |cx| PasswordPrompt::new(prompt, tx, cx));
}
})
.unwrap();
rx
}
fn set_status(&self, status: Option<&str>, cx: &mut AsyncAppContext) {
self.update_status(status, cx)
}
fn get_server_binary(
&self,
platform: SshPlatform,
cx: &mut AsyncAppContext,
) -> oneshot::Receiver<Result<(PathBuf, SemanticVersion)>> {
let (tx, rx) = oneshot::channel();
let this = self.clone();
cx.spawn(|mut cx| async move {
tx.send(this.get_server_binary_impl(platform, &mut cx).await)
.ok();
tx.send(get_server_binary(platform, &mut cx).await).ok();
})
.detach();
rx
@@ -211,63 +200,48 @@ impl remote::SshClientDelegate for SshClientDelegate {
}
}
impl SshClientDelegate {
fn update_status(&self, status: Option<&str>, cx: &mut AsyncAppContext) {
self.window
.update(cx, |_, cx| {
self.modal.update(cx, |modal, cx| {
modal.set_status(status.map(|s| s.to_string()), cx);
});
})
.ok();
}
async fn get_server_binary(
platform: SshPlatform,
cx: &mut AsyncAppContext,
) -> Result<(PathBuf, SemanticVersion)> {
let (version, release_channel) =
cx.update(|cx| (AppVersion::global(cx), ReleaseChannel::global(cx)))?;
async fn get_server_binary_impl(
&self,
platform: SshPlatform,
cx: &mut AsyncAppContext,
) -> Result<(PathBuf, SemanticVersion)> {
let (version, release_channel) =
cx.update(|cx| (AppVersion::global(cx), ReleaseChannel::global(cx)))?;
// In dev mode, build the remote server binary from source
#[cfg(debug_assertions)]
if crate::stdout_is_a_pty()
&& release_channel == ReleaseChannel::Dev
&& platform.arch == std::env::consts::ARCH
&& platform.os == std::env::consts::OS
{
use smol::process::{Command, Stdio};
// In dev mode, build the remote server binary from source
#[cfg(debug_assertions)]
if crate::stdout_is_a_pty()
&& release_channel == ReleaseChannel::Dev
&& platform.arch == std::env::consts::ARCH
&& platform.os == std::env::consts::OS
{
use smol::process::{Command, Stdio};
log::info!("building remote server binary from source");
run_cmd(Command::new("cargo").args(["build", "--package", "remote_server"])).await?;
run_cmd(Command::new("strip").args(["target/debug/remote_server"])).await?;
run_cmd(Command::new("gzip").args(["-9", "-f", "target/debug/remote_server"])).await?;
self.update_status(Some("building remote server binary from source"), cx);
log::info!("building remote server binary from source");
run_cmd(Command::new("cargo").args(["build", "--package", "remote_server"])).await?;
run_cmd(Command::new("strip").args(["target/debug/remote_server"])).await?;
run_cmd(Command::new("gzip").args(["-9", "-f", "target/debug/remote_server"])).await?;
let path = std::env::current_dir()?.join("target/debug/remote_server.gz");
return Ok((path, version));
let path = std::env::current_dir()?.join("target/debug/remote_server.gz");
return Ok((path, version));
async fn run_cmd(command: &mut Command) -> Result<()> {
let output = command.stderr(Stdio::inherit()).output().await?;
if !output.status.success() {
Err(anyhow!("failed to run command: {:?}", command))?;
}
Ok(())
async fn run_cmd(command: &mut Command) -> Result<()> {
let output = command.stderr(Stdio::inherit()).output().await?;
if !output.status.success() {
Err(anyhow!("failed to run command: {:?}", command))?;
}
Ok(())
}
self.update_status(Some("checking for latest version of remote server"), cx);
let binary_path = AutoUpdater::get_latest_remote_server_release(
platform.os,
platform.arch,
release_channel,
cx,
)
.await?;
Ok((binary_path, version))
}
let binary_path = AutoUpdater::get_latest_remote_server_release(
platform.os,
platform.arch,
release_channel,
cx,
)
.await?;
Ok((binary_path, version))
}
#[cfg(target_os = "linux")]
@@ -341,21 +315,12 @@ pub async fn open_ssh_paths(
cx.new_view(|cx| Workspace::new(None, project, app_state.clone(), cx))
})?;
let modal = window.update(cx, |workspace, cx| {
cx.activate_window();
workspace.toggle_modal(cx, |cx| {
SshConnectionModal::new(connection_info.host.clone(), cx)
});
workspace.active_modal::<SshConnectionModal>(cx).unwrap()
})?;
let session = remote::SshSession::client(
connection_info.username,
connection_info.host,
connection_info.port,
Arc::new(SshClientDelegate {
window,
modal,
known_password: connection_info.password,
}),
cx,

View File

@@ -0,0 +1,69 @@
use anyhow::Result;
use editor::Editor;
use futures::channel::oneshot;
use gpui::{
px, DismissEvent, EventEmitter, FocusableView, ParentElement as _, Render, SharedString, View,
};
use ui::{v_flex, InteractiveElement, Label, Styled, StyledExt as _, ViewContext, VisualContext};
use workspace::ModalView;
pub struct PasswordPrompt {
prompt: SharedString,
tx: Option<oneshot::Sender<Result<String>>>,
editor: View<Editor>,
}
impl PasswordPrompt {
pub fn new(
prompt: String,
tx: oneshot::Sender<Result<String>>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
prompt: SharedString::from(prompt),
tx: Some(tx),
editor: cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_redact_all(true, cx);
editor
}),
}
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
let text = self.editor.read(cx).text(cx);
if let Some(tx) = self.tx.take() {
tx.send(Ok(text)).ok();
};
cx.emit(DismissEvent)
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent)
}
}
impl Render for PasswordPrompt {
fn render(&mut self, cx: &mut ui::ViewContext<Self>) -> impl ui::IntoElement {
v_flex()
.key_context("PasswordPrompt")
.elevation_3(cx)
.p_4()
.gap_2()
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::confirm))
.w(px(400.))
.child(Label::new(self.prompt.clone()))
.child(self.editor.clone())
}
}
impl FocusableView for PasswordPrompt {
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for PasswordPrompt {}
impl ModalView for PasswordPrompt {}

View File

@@ -1,97 +0,0 @@
use anyhow::Result;
use editor::Editor;
use futures::channel::oneshot;
use gpui::{
px, DismissEvent, EventEmitter, FocusableView, ParentElement as _, Render, SharedString, View,
};
use ui::{
v_flex, FluentBuilder as _, InteractiveElement, Label, LabelCommon, Styled, StyledExt as _,
ViewContext, VisualContext,
};
use workspace::ModalView;
pub struct SshConnectionModal {
host: SharedString,
status_message: Option<SharedString>,
prompt: Option<(SharedString, oneshot::Sender<Result<String>>)>,
editor: View<Editor>,
}
impl SshConnectionModal {
pub fn new(host: String, cx: &mut ViewContext<Self>) -> Self {
Self {
host: host.into(),
prompt: None,
status_message: None,
editor: cx.new_view(|cx| Editor::single_line(cx)),
}
}
pub fn set_prompt(
&mut self,
prompt: String,
tx: oneshot::Sender<Result<String>>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
if prompt.contains("yes/no") {
editor.set_redact_all(false, cx);
} else {
editor.set_redact_all(true, cx);
}
});
self.prompt = Some((prompt.into(), tx));
self.status_message.take();
cx.focus_view(&self.editor);
cx.notify();
}
pub fn set_status(&mut self, status: Option<String>, cx: &mut ViewContext<Self>) {
self.status_message = status.map(|s| s.into());
cx.notify();
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
if let Some((_, tx)) = self.prompt.take() {
self.editor.update(cx, |editor, cx| {
tx.send(Ok(editor.text(cx))).ok();
editor.clear(cx);
});
}
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.remove_window();
}
}
impl Render for SshConnectionModal {
fn render(&mut self, cx: &mut ui::ViewContext<Self>) -> impl ui::IntoElement {
v_flex()
.key_context("PasswordPrompt")
.elevation_3(cx)
.p_4()
.gap_2()
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::confirm))
.w(px(400.))
.child(Label::new(format!("SSH: {}", self.host)).size(ui::LabelSize::Large))
.when_some(self.status_message.as_ref(), |el, status| {
el.child(Label::new(status.clone()))
})
.when_some(self.prompt.as_ref(), |el, prompt| {
el.child(Label::new(prompt.0.clone()))
.child(self.editor.clone())
})
}
}
impl FocusableView for SshConnectionModal {
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for SshConnectionModal {}
impl ModalView for SshConnectionModal {}

View File

@@ -16,7 +16,6 @@ For more information, see the Pyright [configuration documentation](https://micr
The [pyright](https://github.com/microsoft/pyright) language server also accepts specific LSP related settings, not necessarily connected to a project. These can be changed in the `lsp` section of your `settings.json`.
For example, in order to:
- use strict type-checking level
- diagnose all files in the workspace instead of the only open files default
- provide the path to a specific python interpreter
@@ -94,30 +93,23 @@ You can also configure this option directly in your `settings.json` file ([pyrig
}
```
### Code formatting & Linting
### Code formatting
The Pyright language server does not provide code formatting or linting. If you want to detect lint errors and reformat your Python code upon saving, you'll need to set up.
The Pyright language server does not provide code formatting. If you want to automatically reformat your Python code when saving, you'll need to specify an \_external_code formatter in your settings. See the [configuration](../configuring-zed.md) documentation for more information.
A common tool for formatting Python code is [Ruff](https://black.readthedocs.io/en/stable/). It is another tool written in Rust, an extremely fast Python linter and code formatter.
It is available through the [Ruff extension](https://docs.astral.sh/ruff/). However, the code formatting through the extension is not yet available. You can set up the formatter to run on save by adding the following configuration to your `settings.json`, assuming that [`Ruff`](https://docs.astral.sh/ruff/) is installed in your Python environment.
A common tool for formatting python code is [Black](https://black.readthedocs.io/en/stable/). If you have Black installed globally, you can use it to format Python files by adding the following to your `settings.json`:
```json
{
"languages": {
..., // other languages
"Python": {
"format_on_save": {
"external": {
"command": "python",
"arguments": [
"-m",
"ruff",
"format",
"-"
]
"formatter": {
"external": {
"command": "black",
"arguments": ["-"]
}
}
},
"format_on_save": "on"
}
}
}

View File

@@ -13,4 +13,4 @@ path = "src/erlang.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.6"
zed_extension_api = "0.0.4"

View File

@@ -3,17 +3,13 @@ name = "Erlang"
description = "Erlang support."
version = "0.0.1"
schema_version = 1
authors = ["Dairon M <dairon.medina@gmail.com>", "Fabian Bergström <fabian@fmbb.se>"]
authors = ["Dairon M <dairon.medina@gmail.com>"]
repository = "https://github.com/zed-industries/zed"
[language_servers.erlang-ls]
name = "Erlang Language Server"
language = "Erlang"
[language_servers.elp]
name = "Erlang Language Platform"
language = "Erlang"
[grammars.erlang]
repository = "https://github.com/WhatsApp/tree-sitter-erlang"
commit = "57e69513efd831f9cc8207d65d96bad917ca4aa4"

View File

@@ -1,45 +1,26 @@
mod language_servers;
use zed_extension_api::{self as zed, Result};
use crate::language_servers::{ErlangLanguagePlatform, ErlangLs};
struct ErlangExtension {
erlang_ls: Option<ErlangLs>,
erlang_language_platform: Option<ErlangLanguagePlatform>,
}
struct ErlangExtension;
impl zed::Extension for ErlangExtension {
fn new() -> Self {
Self {
erlang_ls: None,
erlang_language_platform: None,
}
Self
}
fn language_server_command(
&mut self,
language_server_id: &zed::LanguageServerId,
_config: zed::LanguageServerConfig,
worktree: &zed::Worktree,
) -> Result<zed::Command> {
match language_server_id.as_ref() {
ErlangLs::LANGUAGE_SERVER_ID => {
let erlang_ls = self.erlang_ls.get_or_insert_with(|| ErlangLs::new());
let path = worktree
.which("erlang_ls")
.ok_or_else(|| "erlang_ls must be installed and available on your $PATH".to_string())?;
Ok(zed::Command {
command: erlang_ls.language_server_binary_path(language_server_id, worktree)?,
args: vec![],
env: Default::default(),
})
}
ErlangLanguagePlatform::LANGUAGE_SERVER_ID => {
let erlang_language_platform = self
.erlang_language_platform
.get_or_insert_with(|| ErlangLanguagePlatform::new());
erlang_language_platform.language_server_command(language_server_id, worktree)
}
language_server_id => Err(format!("unknown language server: {language_server_id}")),
}
Ok(zed::Command {
command: path,
args: Vec::new(),
env: Default::default(),
})
}
}

View File

@@ -1,5 +0,0 @@
mod erlang_language_platform;
mod erlang_ls;
pub use erlang_language_platform::*;
pub use erlang_ls::*;

View File

@@ -1,112 +0,0 @@
use std::fs;
use zed_extension_api::{self as zed, LanguageServerId, Result};
pub struct ErlangLanguagePlatform {
cached_binary_path: Option<String>,
}
impl ErlangLanguagePlatform {
pub const LANGUAGE_SERVER_ID: &'static str = "elp";
pub fn new() -> Self {
Self {
cached_binary_path: None,
}
}
pub fn language_server_command(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<zed::Command> {
Ok(zed::Command {
command: self.language_server_binary_path(language_server_id, worktree)?,
args: vec!["server".to_string()],
env: Default::default(),
})
}
fn language_server_binary_path(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("elp") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let release = zed::latest_github_release(
"WhatsApp/erlang-language-platform",
zed::GithubReleaseOptions {
require_assets: true,
pre_release: false,
},
)?;
let (platform, arch) = zed::current_platform();
let asset_name = {
let otp_version = "26.2";
let (os, os_target) = match platform {
zed::Os::Mac => ("macos", "apple-darwin"),
zed::Os::Linux => ("linux", "unknown-linux-gnu"),
zed::Os::Windows => return Err(format!("unsupported platform: {platform:?}")),
};
format!(
"elp-{os}-{arch}-{os_target}-otp-{otp_version}.tar.gz",
arch = match arch {
zed::Architecture::Aarch64 => "aarch64",
zed::Architecture::X8664 => "x86_64",
zed::Architecture::X86 =>
return Err(format!("unsupported architecture: {arch:?}")),
},
)
};
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!("elp-{}", release.version);
let binary_path = format!("{version_dir}/elp");
if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::Downloading,
);
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.cached_binary_path = Some(binary_path.clone());
Ok(binary_path)
}
}

View File

@@ -1,21 +0,0 @@
use zed_extension_api::{self as zed, LanguageServerId, Result};
pub struct ErlangLs;
impl ErlangLs {
pub const LANGUAGE_SERVER_ID: &'static str = "erlang-ls";
pub fn new() -> Self {
Self
}
pub fn language_server_binary_path(
&mut self,
_language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
worktree
.which("erlang_ls")
.ok_or_else(|| "erlang_ls must be installed and available on your $PATH".to_string())
}
}

View File

@@ -1,16 +0,0 @@
[package]
name = "zed_ruff"
version = "0.0.1"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/ruff.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.6"

View File

@@ -1 +0,0 @@
../../LICENSE-APACHE

View File

@@ -1,11 +0,0 @@
id = "ruff"
name = "Ruff"
description = "Support for Ruff, the Python linter and formatter"
version = "0.0.1"
schema_version = 1
authors = []
repository = "https://github.com/zed-industries/zed"
[language_servers.ruff]
name = "Ruff"
languages = ["Python"]

View File

@@ -1,134 +0,0 @@
use std::fs;
use zed::LanguageServerId;
use zed_extension_api::{self as zed, settings::LspSettings, Result};
struct RuffExtension {
cached_binary_path: Option<String>,
}
impl RuffExtension {
fn language_server_binary_path(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("ruff") {
return Ok(path);
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let release = zed::latest_github_release(
"astral-sh/ruff",
zed::GithubReleaseOptions {
require_assets: true,
pre_release: false,
},
)?;
let (platform, arch) = zed::current_platform();
let asset_stem = format!(
"ruff-{arch}-{os}",
arch = match arch {
zed::Architecture::Aarch64 => "aarch64",
zed::Architecture::X86 => "x86",
zed::Architecture::X8664 => "x86_64",
},
os = match platform {
zed::Os::Mac => "apple-darwin",
zed::Os::Linux => "unknown-linux-gnu",
zed::Os::Windows => "pc-windows-msvc",
}
);
let asset_name = format!(
"{asset_stem}.{suffix}",
suffix = match platform {
zed::Os::Windows => "zip",
_ => "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!("ruff-{}", release.version);
let binary_path = format!("{version_dir}/{asset_stem}/ruff");
if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::Downloading,
);
let file_kind = match platform {
zed::Os::Windows => zed::DownloadedFileType::Zip,
_ => zed::DownloadedFileType::GzipTar,
};
zed::download_file(&asset.download_url, &version_dir, file_kind)
.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.cached_binary_path = Some(binary_path.clone());
Ok(binary_path)
}
}
impl zed::Extension for RuffExtension {
fn new() -> Self {
Self {
cached_binary_path: None,
}
}
fn language_server_command(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<zed::Command> {
Ok(zed::Command {
command: self.language_server_binary_path(language_server_id, worktree)?,
args: vec!["server".into(), "--preview".into()],
env: vec![],
})
}
fn language_server_initialization_options(
&mut self,
server_id: &LanguageServerId,
worktree: &zed_extension_api::Worktree,
) -> Result<Option<zed_extension_api::serde_json::Value>> {
let settings = LspSettings::for_worktree(server_id.as_ref(), worktree)
.ok()
.and_then(|lsp_settings| lsp_settings.initialization_options.clone())
.unwrap_or_default();
Ok(Some(settings))
}
fn language_server_workspace_configuration(
&mut self,
server_id: &LanguageServerId,
worktree: &zed_extension_api::Worktree,
) -> Result<Option<zed_extension_api::serde_json::Value>> {
let settings = LspSettings::for_worktree(server_id.as_ref(), worktree)
.ok()
.and_then(|lsp_settings| lsp_settings.settings.clone())
.unwrap_or_default();
Ok(Some(settings))
}
}
zed::register_extension!(RuffExtension);

View File

@@ -24,7 +24,7 @@ Build the application bundle for macOS.
Options:
-d Compile in debug mode
-l Compile for local architecture only.
-l Compile for local architecture and copy bundle to /Applications.
-o Open dir with the resulting DMG or launch the app itself in local mode.
-i Install the resulting DMG into /Applications in local mode. Noop without -l.
-h Display this help and exit.
@@ -390,6 +390,6 @@ else
sign_binary "target/x86_64-apple-darwin/release/remote_server"
sign_binary "target/aarch64-apple-darwin/release/remote_server"
gzip --stdout --best target/x86_64-apple-darwin/release/remote_server > target/zed-remote-server-macos-x86_64.gz
gzip --stdout --best target/aarch64-apple-darwin/release/remote_server > target/zed-remote-server-macos-aarch64.gz
gzip --stdout --best target/x86_64-apple-darwin/release/remote_server > target/zed-remote-server-mac-x86_64.gz
gzip --stdout --best target/aarch64-apple-darwin/release/remote_server > target/zed-remote-server-mac-aarch64.gz
fi