Compare commits

..

3 Commits

Author SHA1 Message Date
Conrad Irwin
ada0523043 Fix mouse selection
Co-Authored-By: WindSoilder <WindSoilder@outlook.com>
2024-02-02 16:36:51 -07:00
WindSoilder
aeb8535468 Merge branch 'main' into vim_click 2024-02-01 06:59:01 +08:00
WindSoilder
a082b93260 single click convert from virual mode to normal mode 2024-01-29 20:41:36 +08:00
88 changed files with 415 additions and 2111 deletions

View File

@@ -81,7 +81,6 @@ jobs:
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
steps:
- name: Install Node
uses: actions/setup-node@v3

View File

@@ -1,107 +0,0 @@
name: Publish Collab Server Image
on:
push:
tags:
- collab-production
- collab-staging
env:
DOCKER_BUILDKIT: 1
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
jobs:
style:
name: Check formatting and Clippy lints
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
fetch-depth: 0
- name: Run style checks
uses: ./.github/actions/check_style
tests:
name: Run tests
runs-on:
- self-hosted
- test
needs: style
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
fetch-depth: 0
- name: Run tests
uses: ./.github/actions/run_tests
publish:
name: Publish collab server image
needs:
- style
- tests
runs-on:
- self-hosted
- deploy
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Sign into DigitalOcean docker registry
run: doctl registry login
- name: Prune Docker system
run: docker system prune --filter 'until=720h' -f
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
- name: Build docker image
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
deploy:
name: Deploy new server image
needs:
- publish
runs-on:
- self-hosted
- deploy
steps:
- name: Sign into Kubernetes
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
- name: Determine namespace
run: |
set -eu
if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
echo "Deploying collab:$GITHUB_SHA to production"
echo "KUBE_NAMESPACE=production" >> $GITHUB_ENV
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
echo "Deploying collab:$GITHUB_SHA to staging"
echo "KUBE_NAMESPACE=staging" >> $GITHUB_ENV
else
echo "cowardly refusing to deploy from an unknown branch"
exit 1
fi
- name: Start rollout
run: kubectl -n "$KUBE_NAMESPACE" set image deployment/collab collab=registry.digitalocean.com/zed/collab:${GITHUB_SHA}
- name: Wait for rollout to finish
run: kubectl -n "$KUBE_NAMESPACE" rollout status deployment/collab

View File

@@ -0,0 +1,49 @@
name: Publish Collab Server Image
on:
push:
tags:
- collab-v*
env:
DOCKER_BUILDKIT: 1
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
jobs:
publish:
name: Publish collab server image
runs-on:
- self-hosted
- deploy
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Sign into DigitalOcean docker registry
run: doctl registry login
- name: Prune Docker system
run: docker system prune
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: 'recursive'
- name: Determine version
run: |
set -eu
version=$(script/get-crate-version collab)
if [[ $GITHUB_REF_NAME != "collab-v${version}" ]]; then
echo "release tag ${GITHUB_REF_NAME} does not match version ${version}"
exit 1
fi
echo "Publishing collab version: ${version}"
echo "COLLAB_VERSION=${version}" >> $GITHUB_ENV
- name: Build docker image
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}

44
Cargo.lock generated
View File

@@ -1367,7 +1367,6 @@ dependencies = [
"image",
"lazy_static",
"log",
"once_cell",
"parking_lot 0.11.2",
"postage",
"rand 0.8.5",
@@ -1378,7 +1377,6 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
"sha2 0.10.7",
"smol",
"sum_tree",
"sysinfo",
@@ -1440,7 +1438,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.44.0"
version = "0.43.0"
dependencies = [
"anyhow",
"async-trait",
@@ -1486,7 +1484,6 @@ dependencies = [
"prometheus",
"prost 0.8.0",
"rand 0.8.5",
"release_channel",
"reqwest",
"rpc",
"scrypt",
@@ -2425,7 +2422,6 @@ dependencies = [
"postage",
"project",
"rand 0.8.5",
"release_channel",
"rich_text",
"rpc",
"schemars",
@@ -2662,7 +2658,6 @@ dependencies = [
"env_logger",
"fuzzy",
"gpui",
"itertools 0.11.0",
"language",
"menu",
"picker",
@@ -4022,7 +4017,6 @@ dependencies = [
"language",
"lsp",
"project",
"release_channel",
"serde",
"serde_json",
"settings",
@@ -4267,7 +4261,6 @@ dependencies = [
"lsp-types",
"parking_lot 0.11.2",
"postage",
"release_channel",
"serde",
"serde_derive",
"serde_json",
@@ -4321,26 +4314,6 @@ dependencies = [
"libc",
]
[[package]]
name = "markdown_preview"
version = "0.1.0"
dependencies = [
"anyhow",
"editor",
"gpui",
"language",
"lazy_static",
"log",
"menu",
"project",
"pulldown-cmark",
"rich_text",
"theme",
"ui",
"util",
"workspace",
]
[[package]]
name = "matchers"
version = "0.1.0"
@@ -5822,7 +5795,6 @@ dependencies = [
"pretty_assertions",
"rand 0.8.5",
"regex",
"release_channel",
"rpc",
"schemars",
"serde",
@@ -5887,7 +5859,6 @@ dependencies = [
"picker",
"postage",
"project",
"release_channel",
"serde_json",
"settings",
"smol",
@@ -8812,16 +8783,6 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-erlang"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ced5145ebb17f83243bf055b74e108da7cc129e12faab4166df03f59b287f4"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-gitcommit"
version = "0.3.3"
@@ -9458,7 +9419,6 @@ dependencies = [
"parking_lot 0.11.2",
"project",
"regex",
"release_channel",
"search",
"serde",
"serde_derive",
@@ -10352,7 +10312,6 @@ dependencies = [
"libc",
"log",
"lsp",
"markdown_preview",
"menu",
"mimalloc",
"node_runtime",
@@ -10402,7 +10361,6 @@ dependencies = [
"tree-sitter-elixir",
"tree-sitter-elm",
"tree-sitter-embedded-template",
"tree-sitter-erlang",
"tree-sitter-gitcommit",
"tree-sitter-gleam",
"tree-sitter-glsl",

View File

@@ -42,7 +42,6 @@ members = [
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/multi_buffer",
@@ -112,7 +111,6 @@ parking_lot = "0.11.1"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
prost = "0.8"
pulldown-cmark = { version = "0.9.2", default-features = false }
rand = "0.8.5"
refineable = { path = "./crates/refineable" }
regex = "1.5"
@@ -142,7 +140,6 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
tree-sitter-embedded-template = "0.20.0"
tree-sitter-erlang = "0.4.0"
tree-sitter-gitcommit = { git = "https://github.com/gbprod/tree-sitter-gitcommit" }
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }

View File

@@ -6,9 +6,6 @@ COPY . .
# Compile collab server
ARG CARGO_PROFILE_RELEASE_PANIC=abort
ARG GITHUB_SHA
ENV GITHUB_SHA=$GITHUB_SHA
RUN --mount=type=cache,target=./script/node_modules \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=./target \

View File

@@ -1 +0,0 @@
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,9 +1,7 @@
{
"suffixes": {
"Emakefile": "erlang",
"aac": "audio",
"accdb": "storage",
"app.src": "erlang",
"avif": "image",
"bak": "backup",
"bash": "terminal",
@@ -25,8 +23,6 @@
"doc": "document",
"docx": "document",
"eex": "elixir",
"erl": "erlang",
"escript": "erlang",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
@@ -41,18 +37,17 @@
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitkeep": "vcs",
"gitmodules": "vcs",
"gitkeep": "vcs",
"go": "go",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"heif": "image",
"hrl": "erlang",
"hs": "haskell",
"htm": "template",
"html": "template",
"hs": "haskell",
"ib": "storage",
"ico": "image",
"ini": "settings",
@@ -90,7 +85,6 @@
"psd": "image",
"py": "python",
"rb": "ruby",
"rebar.config": "erlang",
"rkt": "code",
"rs": "rust",
"rtf": "document",
@@ -110,15 +104,13 @@
"txt": "document",
"vue": "vue",
"wav": "audio",
"webm": "video",
"webp": "image",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"xrl": "erlang",
"yaml": "yaml",
"yml": "yaml",
"yrl": "erlang",
"yaml": "settings",
"yml": "settings",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
@@ -141,7 +133,7 @@
"icon": "icons/file_icons/folder.svg"
},
"css": {
"icon": "icons/file_icons/css.svg"
"icon": "icons/file_icons/css.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
@@ -152,9 +144,6 @@
"elixir": {
"icon": "icons/file_icons/elixir.svg"
},
"erlang": {
"icon": "icons/file_icons/erlang.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
@@ -185,9 +174,6 @@
"php": {
"icon": "icons/file_icons/php.svg"
},
"yaml": {
"icon": "icons/file_icons/yaml.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},

View File

@@ -1 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="457px" height="512px"><polygon points="342.0159302,0 457,0 114.9831009,512 0,512 171.0082092,256 0,0 114.9831009,0 228.4997559,169.9342041 "/></svg>

Before

Width:  |  Height:  |  Size: 209 B

View File

@@ -96,8 +96,6 @@
}
}
],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
@@ -335,6 +333,8 @@
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
@@ -502,18 +502,5 @@
"enter": "vim::SearchSubmit",
"escape": "buffer_search::Dismiss"
}
},
{
"context": "Dock",
"bindings": {
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"]
}
}
]

View File

@@ -1,5 +1,5 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
{}

View File

@@ -199,13 +199,9 @@ impl AssistantPanel {
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
cx.notify();
if self.focus_handle.is_focused(cx) {
if self.has_credentials() {
if let Some(editor) = self.active_editor() {
cx.focus_view(editor);
}
}
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
if let Some(editor) = self.active_editor() {
cx.focus_view(editor);
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
cx.focus_view(api_key_editor);
}
}
@@ -781,10 +777,6 @@ impl AssistantPanel {
});
}
fn build_api_key_editor(&mut self, cx: &mut WindowContext<'_>) {
self.api_key_editor = Some(build_api_key_editor(cx));
}
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
let editor = cx.new_view(|cx| {
ConversationEditor::new(
@@ -878,7 +870,7 @@ impl AssistantPanel {
cx.update(|cx| completion_provider.delete_credentials(cx))?
.await;
this.update(&mut cx, |this, cx| {
this.build_api_key_editor(cx);
this.api_key_editor = Some(build_api_key_editor(cx));
this.focus_handle.focus(cx);
cx.notify();
})
@@ -1144,7 +1136,7 @@ impl AssistantPanel {
}
}
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
@@ -1155,10 +1147,9 @@ fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
if let Some(api_key_editor) = self.api_key_editor.clone() {
const INSTRUCTIONS: [&'static str; 6] = [
const INSTRUCTIONS: [&'static str; 5] = [
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
" - You can create an API key at: platform.openai.com/api-keys",
" - Make sure your OpenAI account has credits",
" - Having a subscription for another service like GitHub Copilot won't work.",
" ",
"Paste your OpenAI API key and press Enter to use the assistant:"
@@ -1351,9 +1342,7 @@ impl Panel for AssistantPanel {
cx.spawn(|this, mut cx| async move {
load_credentials.await;
this.update(&mut cx, |this, cx| {
if !this.has_credentials() {
this.build_api_key_editor(cx);
} else if this.editors.is_empty() {
if this.editors.is_empty() {
this.new_conversation(cx);
}
})

View File

@@ -1,7 +1,7 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings, ZED_APP_PATH};
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
@@ -108,28 +108,29 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
})
.detach();
let version = release_channel::AppVersion::global(cx);
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
.then(|| updater.start_polling(cx));
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
.then(|| updater.start_polling(cx));
cx.observe_global::<SettingsStore>(move |updater, cx| {
if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
cx.observe_global::<SettingsStore>(move |updater, cx| {
if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
}
} else {
update_subscription.take();
}
} else {
update_subscription.take();
}
})
.detach();
})
.detach();
updater
});
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
updater
});
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
}
pub fn check(_: &Check, cx: &mut WindowContext) {

View File

@@ -329,7 +329,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
let http = FakeHttpClient::with_404_response();

View File

@@ -32,7 +32,6 @@ futures.workspace = true
image = "0.23"
lazy_static.workspace = true
log.workspace = true
once_cell = "1.19.0"
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
@@ -40,7 +39,6 @@ schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha2 = "0.10"
smol.workspace = true
sysinfo.workspace = true
tempfile.workspace = true

View File

@@ -15,13 +15,14 @@ use futures::{
TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, SemanticVersion,
Task, WeakModel,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use release_channel::{AppVersion, ReleaseChannel};
use release_channel::ReleaseChannel;
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -57,6 +58,9 @@ lazy_static! {
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
.ok()
.and_then(|s| if s.is_empty() { None } else { Some(s) });
pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
.ok()
.and_then(|v| v.parse().ok());
pub static ref ZED_APP_PATH: Option<PathBuf> =
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
pub static ref ZED_ALWAYS_ACTIVE: bool =
@@ -1007,22 +1011,13 @@ impl Client {
.update(|cx| ReleaseChannel::try_global(cx))
.ok()
.flatten();
let app_version = cx
.update(|cx| AppVersion::global(cx).to_string())
.ok()
.unwrap_or_default();
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
.header("x-zed-app-version", app_version)
.header(
"x-zed-release-channel",
release_channel.map(|r| r.dev_name()).unwrap_or("unknown"),
);
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
let http = self.http.clone();
cx.background_executor().spawn(async move {

View File

@@ -4,19 +4,16 @@ use crate::TelemetrySettings;
use chrono::{DateTime, Utc};
use futures::Future;
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use serde::Serialize;
use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::io::Write;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
};
use tempfile::NamedTempFile;
use util::http::{self, HttpClient, Method, ZedHttpClient};
use util::http::{HttpClient, ZedHttpClient};
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
@@ -145,13 +142,6 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(not(debug_assertions))]
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Vec<u8>> = Lazy::new(|| {
option_env!("ZED_CLIENT_CHECKSUM_SEED")
.unwrap_or("development-checksum-seed")
.as_bytes()
.into()
});
impl Telemetry {
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
let release_channel =
@@ -550,27 +540,9 @@ impl Telemetry {
serde_json::to_writer(&mut json_bytes, &request_body)?;
}
let mut summer = Sha256::new();
summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
summer.update(&json_bytes);
summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
let mut checksum = String::new();
for byte in summer.finalize().as_slice() {
use std::fmt::Write;
write!(&mut checksum, "{:02x}", byte).unwrap();
}
let request = http::Request::builder()
.method(Method::POST)
.uri(&this.http_client.zed_url("/api/events"))
.header("Content-Type", "text/plain")
.header("x-zed-checksum", checksum)
.body(json_bytes.into());
let response = this.http_client.send(request?).await?;
if response.status() != 200 {
log::error!("Failed to send events: HTTP {:?}", response.status());
}
this.http_client
.post_json(&this.http_client.zed_url("/api/events"), json_bytes.into())
.await?;
anyhow::Ok(())
}
.log_err(),

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.44.0"
version = "0.43.0"
publish = false
license = "AGPL-3.0-or-later"
@@ -61,7 +61,6 @@ util = { path = "../util" }
uuid.workspace = true
[dev-dependencies]
release_channel = { path = "../release_channel" }
async-trait.workspace = true
audio = { path = "../audio" }
call = { path = "../call", features = ["test-support"] }

View File

@@ -3,35 +3,3 @@
This crate is what we run at https://collab.zed.dev.
It contains our back-end logic for collaboration, to which we connect from the Zed client via a websocket after authenticating via https://zed.dev, which is a separate repo running on Vercel.
# Local Development
Detailed instructions on getting started are [here](https://zed.dev/docs/local-collaboration).
# Deployment
We run two instances of collab:
* Staging (https://staging-collab.zed.dev)
* Production (https://collab.zed.dev)
Both of these run on the Kubernetes cluster hosted in Digital Ocean.
Deployment is triggered by pushing to the `collab-staging` (or `collab-production`) tag in Github. The best way to do this is:
* `./script/deploy-collab staging`
* `./script/deploy-collab production`
You can tell what is currently deployed with `./script/what-is-deployed`.
# Database Migrations
To create a new migration:
```
./script/sqlx migrate add <name>
```
Migrations are run automatically on service start, so run `foreman start` again. The service will crash if the migrations fail.
When you create a new migration, you also need to update the [SQLite schema](./migrations.sqlite/20221109000000_test_schema.sql) that is used for testing.

View File

@@ -14,7 +14,6 @@ use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
use util::ResultExt;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
#[tokio::main]
async fn main() -> Result<()> {
@@ -27,7 +26,7 @@ async fn main() -> Result<()> {
match args().skip(1).next().as_deref() {
Some("version") => {
println!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"));
println!("collab v{VERSION}");
}
Some("migrate") => {
run_migrations().await?;
@@ -106,7 +105,7 @@ async fn run_migrations() -> Result<()> {
}
async fn handle_root() -> String {
format!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"))
format!("collab v{VERSION}")
}
async fn handle_liveness_probe(Extension(state): Extension<Arc<AppState>>) -> Result<String> {

View File

@@ -64,7 +64,6 @@ use time::OffsetDateTime;
use tokio::sync::{watch, Semaphore};
use tower::ServiceBuilder;
use tracing::{field, info_span, instrument, Instrument};
use util::SemanticVersion;
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
@@ -796,7 +795,6 @@ fn broadcast<F>(
lazy_static! {
static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
static ref ZED_APP_VERSION: HeaderName = HeaderName::from_static("x-zed-app-version");
}
pub struct ProtocolVersion(u32);
@@ -826,32 +824,6 @@ impl Header for ProtocolVersion {
}
}
pub struct AppVersionHeader(SemanticVersion);
impl Header for AppVersionHeader {
fn name() -> &'static HeaderName {
&ZED_APP_VERSION
}
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i axum::http::HeaderValue>,
{
let version = values
.next()
.ok_or_else(axum::headers::Error::invalid)?
.to_str()
.map_err(|_| axum::headers::Error::invalid())?
.parse()
.map_err(|_| axum::headers::Error::invalid())?;
Ok(Self(version))
}
fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
values.extend([self.0.to_string().parse().unwrap()]);
}
}
pub fn routes(server: Arc<Server>) -> Router<Body> {
Router::new()
.route("/rpc", get(handle_websocket_request))
@@ -866,7 +838,6 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
pub async fn handle_websocket_request(
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
_app_version_header: Option<TypedHeader<AppVersionHeader>>,
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
Extension(server): Extension<Arc<Server>>,
Extension(user): Extension<User>,
@@ -880,7 +851,6 @@ pub async fn handle_websocket_request(
)
.into_response();
}
let socket_address = socket_address.to_string();
ws.on_upgrade(move |socket| {
use util::ResultExt;

View File

@@ -153,7 +153,6 @@ impl TestServer {
}
let settings = SettingsStore::test(cx);
cx.set_global(settings);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
});

View File

@@ -453,7 +453,7 @@ impl ChatPanel {
})
.collect::<Vec<_>>();
rich_text::render_rich_text(message.body.clone(), &mentions, language_registry, None)
rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
}
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {

View File

@@ -14,13 +14,13 @@ pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
WindowContext, WindowKind, WindowOptions,
WindowKind, WindowOptions,
};
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use settings::Settings;
use workspace::{notifications::DetachAndPromptErr, AppState};
use workspace::AppState;
actions!(
collab,
@@ -41,7 +41,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notifications::init(&app_state, cx);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
@@ -64,7 +64,7 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
room.share_screen(cx)
}
});
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
toggle_screen_sharing.detach_and_log_err(cx);
}
}

View File

@@ -445,7 +445,7 @@ impl Copilot {
)
.detach();
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
let server = server.initialize(Default::default()).await?;
let status = server
.request::<request::CheckStatus>(request::CheckStatusParams {

View File

@@ -79,7 +79,6 @@ language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
release_channel = { path = "../release_channel" }
rand.workspace = true
settings = { path = "../settings", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }

View File

@@ -74,7 +74,7 @@ use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
@@ -7289,7 +7289,9 @@ impl Editor {
editor.buffer.read(cx).as_singleton().and_then(|buffer| {
project
.language_server_for_buffer(buffer.read(cx), server_id, cx)
.map(|(lsp_adapter, _)| lsp_adapter.name.clone())
.map(|(_, lsp_adapter)| {
LanguageServerName(Arc::from(lsp_adapter.name()))
})
});
language_server_name.map(|language_server_name| {
project.open_local_buffer_via_lsp(

View File

@@ -8392,7 +8392,6 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
language::init(cx);
Project::init_settings(cx);

View File

@@ -3216,7 +3216,6 @@ pub mod tests {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
language::init(cx);
Project::init_settings(cx);

View File

@@ -1,13 +1,14 @@
use client::ZED_APP_VERSION;
use gpui::AppContext;
use human_bytes::human_bytes;
use release_channel::{AppVersion, ReleaseChannel};
use release_channel::ReleaseChannel;
use serde::Serialize;
use std::{env, fmt::Display};
use sysinfo::{RefreshKind, System, SystemExt};
#[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs {
app_version: String,
app_version: Option<String>,
release_channel: &'static str,
os_name: &'static str,
os_version: Option<String>,
@@ -17,7 +18,9 @@ pub struct SystemSpecs {
impl SystemSpecs {
pub fn new(cx: &AppContext) -> Self {
let app_version = AppVersion::global(cx).to_string();
let app_version = ZED_APP_VERSION
.or_else(|| cx.app_metadata().app_version)
.map(|v| v.to_string());
let release_channel = ReleaseChannel::global(cx).display_name();
let os_name = cx.app_metadata().os_name;
let system = System::new_with_specifics(RefreshKind::new().with_memory());
@@ -45,15 +48,18 @@ impl Display for SystemSpecs {
Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
None => format!("OS: {}", self.os_name),
};
let app_version_information =
format!("Zed: v{} ({})", self.app_version, self.release_channel);
let app_version_information = self
.app_version
.as_ref()
.map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel));
let system_specs = [
app_version_information,
os_information,
format!("Memory: {}", human_bytes(self.memory as f64)),
format!("Architecture: {}", self.architecture),
Some(os_information),
Some(format!("Memory: {}", human_bytes(self.memory as f64))),
Some(format!("Architecture: {}", self.architecture)),
]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join("\n");

View File

@@ -15,7 +15,6 @@ collections = { path = "../collections" }
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
itertools = "0.11"
menu = { path = "../menu" }
picker = { path = "../picker" }
postage.workspace = true

View File

@@ -1,14 +1,13 @@
#[cfg(test)]
mod file_finder_tests;
use collections::{HashMap, HashSet};
use collections::HashMap;
use editor::{scroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use itertools::Itertools;
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
use std::{
@@ -65,15 +64,33 @@ impl FileFinder {
FoundPath::new(project_path, abs_path)
});
let history_items = workspace
.recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
// if exists, bubble the currently opened path to the top
let history_items = currently_opened_path
.clone()
.into_iter()
.filter(|(_, history_abs_path)| match history_abs_path {
Some(abs_path) => history_file_exists(abs_path),
None => true,
})
.map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path))
.collect::<Vec<_>>();
.chain(
workspace
.recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
.into_iter()
.filter(|(history_path, _)| {
Some(history_path)
!= currently_opened_path
.as_ref()
.map(|found_path| &found_path.project)
})
.filter(|(_, history_abs_path)| {
history_abs_path.as_ref()
!= currently_opened_path
.as_ref()
.and_then(|found_path| found_path.absolute.as_ref())
})
.filter(|(_, history_abs_path)| match history_abs_path {
Some(abs_path) => history_file_exists(abs_path),
None => true,
})
.map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
)
.collect();
let project = workspace.project().clone();
let weak_workspace = cx.view().downgrade();
@@ -122,7 +139,7 @@ pub struct FileFinderDelegate {
latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
currently_opened_path: Option<FoundPath>,
matches: Matches,
selected_index: usize,
selected_index: Option<usize>,
cancel_flag: Arc<AtomicBool>,
history_items: Vec<FoundPath>,
}
@@ -192,21 +209,31 @@ impl Matches {
fn push_new_matches(
&mut self,
history_items: &Vec<FoundPath>,
currently_opened: Option<&FoundPath>,
query: &PathLikeWithPosition<FileSearchQuery>,
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
extend_old_matches: bool,
) {
let matching_history_paths =
matching_history_item_paths(history_items, currently_opened, query);
let matching_history_paths = matching_history_item_paths(history_items, query);
let new_search_matches = new_search_matches
.filter(|path_match| !matching_history_paths.contains_key(&path_match.0.path));
self.set_new_history(
currently_opened,
Some(&matching_history_paths),
history_items,
let history_items_to_show = history_items.iter().filter_map(|history_item| {
Some((
history_item.clone(),
Some(
matching_history_paths
.get(&history_item.project.path)?
.clone(),
),
))
});
self.history.clear();
util::extend_sorted(
&mut self.history,
history_items_to_show,
100,
|(_, a), (_, b)| b.cmp(a),
);
if extend_old_matches {
self.search
.retain(|path_match| !matching_history_paths.contains_key(&path_match.0.path));
@@ -215,52 +242,14 @@ impl Matches {
}
util::extend_sorted(&mut self.search, new_search_matches, 100, |a, b| b.cmp(a));
}
fn set_new_history<'a>(
&mut self,
currently_opened: Option<&'a FoundPath>,
query_matches: Option<&'a HashMap<Arc<Path>, ProjectPanelOrdMatch>>,
history_items: impl IntoIterator<Item = &'a FoundPath> + 'a,
) {
let mut processed_paths = HashSet::default();
self.history = history_items
.into_iter()
.chain(currently_opened)
.filter(|&path| processed_paths.insert(path))
.filter_map(|history_item| match &query_matches {
Some(query_matches) => Some((
history_item.clone(),
Some(query_matches.get(&history_item.project.path)?.clone()),
)),
None => Some((history_item.clone(), None)),
})
.enumerate()
.sorted_by(
|(index_a, (path_a, match_a)), (index_b, (path_b, match_b))| match (
Some(path_a) == currently_opened,
Some(path_b) == currently_opened,
) {
// bubble currently opened files to the top
(true, false) => cmp::Ordering::Less,
(false, true) => cmp::Ordering::Greater,
// arrange the files by their score (best score on top) and by their occurrence in the history
// (history items visited later are on the top)
_ => match_b.cmp(match_a).then(index_a.cmp(index_b)),
},
)
.map(|(_, paths)| paths)
.collect();
}
}
fn matching_history_item_paths(
history_items: &Vec<FoundPath>,
currently_opened: Option<&FoundPath>,
query: &PathLikeWithPosition<FileSearchQuery>,
) -> HashMap<Arc<Path>, ProjectPanelOrdMatch> {
let history_items_by_worktrees = history_items
.iter()
.chain(currently_opened)
.filter_map(|found_path| {
let candidate = PathMatchCandidate {
path: &found_path.project.path,
@@ -312,7 +301,7 @@ fn matching_history_item_paths(
matching_history_paths
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Debug, Clone, PartialEq, Eq)]
struct FoundPath {
project: ProjectPath,
absolute: Option<PathBuf>,
@@ -383,7 +372,7 @@ impl FileFinderDelegate {
latest_search_query: None,
currently_opened_path,
matches: Matches::default(),
selected_index: 0,
selected_index: None,
cancel_flag: Arc::new(AtomicBool::new(false)),
history_items,
}
@@ -438,6 +427,7 @@ impl FileFinderDelegate {
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
picker
.update(&mut cx, |picker, cx| {
picker.delegate.selected_index.take();
picker
.delegate
.set_search_matches(search_id, did_cancel, query, matches, cx)
@@ -464,14 +454,12 @@ impl FileFinderDelegate {
.map(|query| query.path_like.path_query());
self.matches.push_new_matches(
&self.history_items,
self.currently_opened_path.as_ref(),
&query,
matches.into_iter(),
extend_old_matches,
);
self.latest_search_query = Some(query);
self.latest_search_did_cancel = did_cancel;
self.selected_index = self.calculate_selected_index();
cx.notify();
}
}
@@ -642,19 +630,6 @@ impl FileFinderDelegate {
.log_err();
})
}
/// Skips first history match (that is displayed topmost) if it's currently opened.
fn calculate_selected_index(&self) -> usize {
if let Some(Match::History(path, _)) = self.matches.get(0) {
if Some(path) == self.currently_opened_path.as_ref() {
let elements_after_first = self.matches.len() - 1;
if elements_after_first > 0 {
return 1;
}
}
}
0
}
}
impl PickerDelegate for FileFinderDelegate {
@@ -669,11 +644,11 @@ impl PickerDelegate for FileFinderDelegate {
}
fn selected_index(&self) -> usize {
self.selected_index
self.selected_index.unwrap_or(0)
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
self.selected_index = Some(ix);
cx.notify();
}
@@ -696,22 +671,22 @@ impl PickerDelegate for FileFinderDelegate {
if raw_query.is_empty() {
let project = self.project.read(cx);
self.latest_search_id = post_inc(&mut self.search_count);
self.selected_index.take();
self.matches = Matches {
history: Vec::new(),
history: self
.history_items
.iter()
.filter(|history_item| {
project
.worktree_for_id(history_item.project.worktree_id, cx)
.is_some()
|| (project.is_local() && history_item.absolute.is_some())
})
.cloned()
.map(|p| (p, None))
.collect(),
search: Vec::new(),
};
self.matches.set_new_history(
self.currently_opened_path.as_ref(),
None,
self.history_items.iter().filter(|history_item| {
project
.worktree_for_id(history_item.project.worktree_id, cx)
.is_some()
|| (project.is_local() && history_item.absolute.is_some())
}),
);
self.selected_index = self.calculate_selected_index();
cx.notify();
Task::ready(())
} else {

View File

@@ -1062,177 +1062,6 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"test": {
"1_qw": "",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
// Open new buffer
open_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_match_selection(&finder, 0, "1_qw");
});
}
#[gpui::test]
async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
cx: &mut TestAppContext,
) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/src",
json!({
"test": {
"bar.rs": "// Bar file",
"lib.rs": "// Lib file",
"maaa.rs": "// Maaaaaaa",
"main.rs": "// Main file",
"moo.rs": "// Moooooo",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
// main.rs is on top, previously used is selected
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "main.rs");
assert_match_selection(finder, 1, "lib.rs");
assert_match_at_position(finder, 2, "bar.rs");
});
// all files match, main.rs is still on top
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches(".rs".to_string(), cx)
})
.await;
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 5);
assert_match_at_position(finder, 0, "main.rs");
assert_match_selection(finder, 1, "bar.rs");
});
// main.rs is not among matches, select top item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("b".to_string(), cx)
})
.await;
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "bar.rs");
});
// main.rs is back, put it on top and select next item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("m".to_string(), cx)
})
.await;
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "main.rs");
assert_match_selection(finder, 1, "moo.rs");
});
// get back to the initial state
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("".to_string(), cx)
})
.await;
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "main.rs");
assert_match_selection(finder, 1, "lib.rs");
});
}
#[gpui::test]
async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/test",
json!({
"test": {
"1.txt": "// One",
"2.txt": "// Two",
"3.txt": "// Three",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
open_queried_buffer("3", 1, "3.txt", &workspace, cx).await;
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "3.txt");
assert_match_selection(finder, 1, "2.txt");
assert_match_at_position(finder, 2, "1.txt");
});
cx.dispatch_action(Confirm); // Open 2.txt
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "2.txt");
assert_match_selection(finder, 1, "3.txt");
assert_match_at_position(finder, 2, "1.txt");
});
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm); // Open 1.txt
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "1.txt");
assert_match_selection(finder, 1, "2.txt");
assert_match_at_position(finder, 2, "3.txt");
});
}
#[gpui::test]
async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
@@ -1343,27 +1172,6 @@ async fn open_close_queried_buffer(
expected_editor_title: &str,
workspace: &View<Workspace>,
cx: &mut gpui::VisualTestContext,
) -> Vec<FoundPath> {
let history_items = open_queried_buffer(
input,
expected_matches,
expected_editor_title,
workspace,
cx,
)
.await;
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
history_items
}
async fn open_queried_buffer(
input: &str,
expected_matches: usize,
expected_editor_title: &str,
workspace: &View<Workspace>,
cx: &mut gpui::VisualTestContext,
) -> Vec<FoundPath> {
let picker = open_file_picker(&workspace, cx);
cx.simulate_input(input);
@@ -1378,6 +1186,7 @@ async fn open_queried_buffer(
finder.delegate.history_items.clone()
});
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm);
cx.read(|cx| {
@@ -1389,6 +1198,8 @@ async fn open_queried_buffer(
);
});
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
history_items
}
@@ -1502,37 +1313,3 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
.collect(),
}
}
#[track_caller]
fn assert_match_selection(
finder: &Picker<FileFinderDelegate>,
expected_selection_index: usize,
expected_file_name: &str,
) {
assert_eq!(
finder.delegate.selected_index(),
expected_selection_index,
"Match is not selected"
);
assert_match_at_position(finder, expected_selection_index, expected_file_name);
}
#[track_caller]
fn assert_match_at_position(
finder: &Picker<FileFinderDelegate>,
match_index: usize,
expected_file_name: &str,
) {
let match_item = finder
.delegate
.matches
.get(match_index)
.unwrap_or_else(|| panic!("Finder has no match for index {match_index}"));
let match_file_name = match match_item {
Match::History(found_path, _) => found_path.absolute.as_deref().unwrap().file_name(),
Match::Search(path_match) => path_match.0.path.file_name(),
}
.unwrap()
.to_string_lossy();
assert_eq!(match_file_name, expected_file_name);
}

View File

@@ -1,32 +0,0 @@
use gpui::*;
struct Counter {
count: usize,
}
impl Counter {
fn new(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|_cx| Self { count: 0 })
}
}
impl Render for Counter {
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
div()
.size_full()
.flex()
.justify_center()
.items_center()
.text_xl()
.bg(rgb(0x2d004b))
.text_color(rgb(0xffffff))
.child(self.count.to_string())
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
cx.open_window(WindowOptions::default(), Counter::new);
cx.activate(true);
});
}

View File

@@ -11,7 +11,7 @@ use crate::{
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
SharedString, Size, Task, TaskLabel, WindowContext,
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use async_task::Runnable;
use futures::channel::oneshot;
use parking::Unparker;
@@ -23,10 +23,11 @@ use std::hash::{Hash, Hasher};
use std::time::Duration;
use std::{
any::Any,
fmt::{self, Debug},
fmt::{self, Debug, Display},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
sync::Arc,
};
use uuid::Uuid;
@@ -38,7 +39,6 @@ pub(crate) use mac::*;
#[cfg(any(test, feature = "test-support"))]
pub(crate) use test::*;
use time::UtcOffset;
pub use util::SemanticVersion;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
@@ -697,6 +697,45 @@ impl Default for CursorStyle {
}
}
/// A datastructure representing a semantic version number
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SemanticVersion {
major: usize,
minor: usize,
patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {

View File

@@ -61,16 +61,6 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
constant Quad *quads
[[buffer(QuadInputIndex_Quads)]]) {
Quad quad = quads[input.quad_id];
// Fast path when the quad is not rounded and doesn't have any border.
if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
quad.corner_radii.top_right == 0. &&
quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
quad.border_widths.bottom == 0.) {
return input.background_color;
}
float2 half_size =
float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
float2 center =

View File

@@ -29,7 +29,7 @@ async-trait.workspace = true
clock = { path = "../clock" }
collections = { path = "../collections" }
futures.workspace = true
fuzzy = { path = "../fuzzy" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
globset.workspace = true
gpui = { path = "../gpui" }
@@ -38,6 +38,7 @@ log.workspace = true
lsp = { path = "../lsp" }
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand = { workspace = true, optional = true }
regex.workspace = true
rpc = { path = "../rpc" }
@@ -54,7 +55,6 @@ text = { path = "../text" }
theme = { path = "../theme" }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
pulldown-cmark.workspace = true
tree-sitter.workspace = true
unicase = "2.6"
util = { path = "../util" }

View File

@@ -30,7 +30,6 @@ workspace = { path = "../workspace" }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
release_channel = { path = "../release_channel" }
env_logger.workspace = true
gpui = { path = "../gpui", features = ["test-support"] }
unindent.workspace = true

View File

@@ -100,7 +100,6 @@ fn init_test(cx: &mut gpui::TestAppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx);
language::init(cx);
client::init_settings(cx);
Project::init_settings(cx);

View File

@@ -27,7 +27,6 @@ serde_derive.workspace = true
serde_json.workspace = true
smol.workspace = true
util = { path = "../util" }
release_channel = { path = "../release_channel" }
[dev-dependencies]
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }

View File

@@ -5,7 +5,7 @@ pub use lsp_types::*;
use anyhow::{anyhow, Context, Result};
use collections::HashMap;
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
use gpui::{AsyncAppContext, BackgroundExecutor, Task};
use parking_lot::Mutex;
use postage::{barrier, prelude::Stream};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -322,15 +322,8 @@ impl LanguageServer {
let mut buffer = Vec::new();
loop {
buffer.clear();
if stdout.read_until(b'\n', &mut buffer).await? == 0 {
break;
};
if stdout.read_until(b'\n', &mut buffer).await? == 0 {
break;
};
stdout.read_until(b'\n', &mut buffer).await?;
stdout.read_until(b'\n', &mut buffer).await?;
let header = std::str::from_utf8(&buffer)?;
let message_len: usize = header
.strip_prefix(CONTENT_LEN_HEADER)
@@ -385,8 +378,6 @@ impl LanguageServer {
// Don't starve the main thread when receiving lots of messages at once.
smol::future::yield_now().await;
}
Ok(())
}
async fn handle_stderr<Stderr>(
@@ -402,12 +393,7 @@ impl LanguageServer {
loop {
buffer.clear();
let bytes_read = stderr.read_until(b'\n', &mut buffer).await?;
if bytes_read == 0 {
return Ok(());
}
stderr.read_until(b'\n', &mut buffer).await?;
if let Ok(message) = str::from_utf8(&buffer) {
log::trace!("incoming stderr message:{message}");
for handler in io_handlers.lock().values_mut() {
@@ -464,11 +450,7 @@ impl LanguageServer {
/// Note that `options` is used directly to construct [`InitializeParams`], which is why it is owned.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize)
pub fn initialize(
mut self,
options: Option<Value>,
cx: &AppContext,
) -> Task<Result<Arc<Self>>> {
pub async fn initialize(mut self, options: Option<Value>) -> Result<Arc<Self>> {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
let params = InitializeParams {
@@ -597,25 +579,18 @@ impl LanguageServer {
uri: root_uri,
name: Default::default(),
}]),
client_info: Some(ClientInfo {
name: release_channel::ReleaseChannel::global(cx)
.display_name()
.to_string(),
version: Some(release_channel::AppVersion::global(cx).to_string()),
}),
client_info: None,
locale: None,
};
cx.spawn(|_| async move {
let response = self.request::<request::Initialize>(params).await?;
if let Some(info) = response.server_info {
self.name = info.name;
}
self.capabilities = response.capabilities;
let response = self.request::<request::Initialize>(params).await?;
if let Some(info) = response.server_info {
self.name = info.name;
}
self.capabilities = response.capabilities;
self.notify::<notification::Initialized>(InitializedParams {})?;
Ok(Arc::new(self))
})
self.notify::<notification::Initialized>(InitializedParams {})?;
Ok(Arc::new(self))
}
/// Sends a shutdown request to the language server process and prepares the [`LanguageServer`] to be dropped.
@@ -1238,9 +1213,6 @@ mod tests {
#[gpui::test]
async fn test_fake(cx: &mut TestAppContext) {
cx.update(|cx| {
release_channel::init("0.0.0", cx);
});
let (server, mut fake) =
FakeLanguageServer::new("the-lsp".to_string(), Default::default(), cx.to_async());
@@ -1257,7 +1229,7 @@ mod tests {
})
.detach();
let server = cx.update(|cx| server.initialize(None, cx)).await.unwrap();
let server = server.initialize(None).await.unwrap();
server
.notify::<notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
text_document: TextDocumentItem::new(

View File

@@ -1,31 +0,0 @@
[package]
name = "markdown_preview"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/markdown_preview.rs"
[features]
test-support = []
[dependencies]
anyhow.workspace = true
editor = { path = "../editor" }
gpui = { path = "../gpui" }
language = { path = "../language" }
lazy_static.workspace = true
log.workspace = true
menu = { path = "../menu" }
project = { path = "../project" }
pulldown-cmark.workspace = true
rich_text = { path = "../rich_text" }
theme = { path = "../theme" }
ui = { path = "../ui" }
util = { path = "../util" }
workspace = { path = "../workspace" }
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

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

View File

@@ -1,14 +0,0 @@
use gpui::{actions, AppContext};
use workspace::Workspace;
pub mod markdown_preview_view;
pub mod markdown_renderer;
actions!(markdown, [OpenPreview]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, cx| {
markdown_preview_view::MarkdownPreviewView::register(workspace, cx);
})
.detach();
}

View File

@@ -1,137 +0,0 @@
use editor::{Editor, EditorEvent};
use gpui::{
canvas, AnyElement, AppContext, AvailableSpace, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, IntoElement, ParentElement, Render, Styled, View, ViewContext,
};
use language::LanguageRegistry;
use std::sync::Arc;
use ui::prelude::*;
use workspace::item::Item;
use workspace::Workspace;
use crate::{markdown_renderer::render_markdown, OpenPreview};
pub struct MarkdownPreviewView {
focus_handle: FocusHandle,
languages: Arc<LanguageRegistry>,
contents: String,
}
impl MarkdownPreviewView {
pub fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
let languages = workspace.app_state().languages.clone();
workspace.register_action(move |workspace, _: &OpenPreview, cx| {
if workspace.has_active_modal(cx) {
cx.propagate();
return;
}
let languages = languages.clone();
if let Some(editor) = workspace.active_item_as::<Editor>(cx) {
let view: View<MarkdownPreviewView> =
cx.new_view(|cx| MarkdownPreviewView::new(editor, languages, cx));
workspace.split_item(workspace::SplitDirection::Right, Box::new(view.clone()), cx);
cx.notify();
}
});
}
pub fn new(
active_editor: View<Editor>,
languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
cx.subscribe(&active_editor, |this, editor, event: &EditorEvent, cx| {
if *event == EditorEvent::Edited {
let editor = editor.read(cx);
let contents = editor.buffer().read(cx).snapshot(cx).text();
this.contents = contents;
cx.notify();
}
})
.detach();
let editor = active_editor.read(cx);
let contents = editor.buffer().read(cx).snapshot(cx).text();
Self {
focus_handle,
languages,
contents,
}
}
}
impl FocusableView for MarkdownPreviewView {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PreviewEvent {}
impl EventEmitter<PreviewEvent> for MarkdownPreviewView {}
impl Item for MarkdownPreviewView {
type Event = PreviewEvent;
fn tab_content(
&self,
_detail: Option<usize>,
selected: bool,
_cx: &WindowContext,
) -> AnyElement {
h_flex()
.gap_2()
.child(Icon::new(IconName::FileDoc).color(if selected {
Color::Default
} else {
Color::Muted
}))
.child(Label::new("Markdown preview").color(if selected {
Color::Default
} else {
Color::Muted
}))
.into_any()
}
fn telemetry_event_text(&self) -> Option<&'static str> {
Some("markdown preview")
}
fn to_item_events(_event: &Self::Event, _f: impl FnMut(workspace::item::ItemEvent)) {}
}
impl Render for MarkdownPreviewView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let rendered_markdown = v_flex()
.items_start()
.justify_start()
.key_context("MarkdownPreview")
.track_focus(&self.focus_handle)
.id("MarkdownPreview")
.overflow_y_scroll()
.overflow_x_hidden()
.size_full()
.bg(cx.theme().colors().editor_background)
.p_4()
.children(render_markdown(&self.contents, &self.languages, cx));
div().flex_1().child(
// FIXME: This shouldn't be necessary
// but the overflow_scroll above doesn't seem to work without it
canvas(move |bounds, cx| {
rendered_markdown.into_any().draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
)
})
.size_full(),
)
}
}

View File

@@ -1,346 +0,0 @@
use std::{ops::Range, sync::Arc};
use gpui::{
div, px, rems, AnyElement, DefiniteLength, Div, ElementId, Hsla, ParentElement, SharedString,
Styled, StyledText, WindowContext,
};
use language::LanguageRegistry;
use pulldown_cmark::{Alignment, CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
use rich_text::render_rich_text;
use theme::{ActiveTheme, Theme};
use ui::{h_flex, v_flex};
enum TableState {
Header,
Body,
}
struct MarkdownTable {
column_alignments: Vec<Alignment>,
header: Vec<Div>,
body: Vec<Vec<Div>>,
current_row: Vec<Div>,
state: TableState,
border_color: Hsla,
}
impl MarkdownTable {
fn new(border_color: Hsla, column_alignments: Vec<Alignment>) -> Self {
Self {
column_alignments,
header: Vec::new(),
body: Vec::new(),
current_row: Vec::new(),
state: TableState::Header,
border_color,
}
}
fn finish_row(&mut self) {
match self.state {
TableState::Header => {
self.header.extend(self.current_row.drain(..));
self.state = TableState::Body;
}
TableState::Body => {
self.body.push(self.current_row.drain(..).collect());
}
}
}
fn add_cell(&mut self, contents: AnyElement) {
let container = match self.alignment_for_next_cell() {
Alignment::Left | Alignment::None => div(),
Alignment::Center => v_flex().items_center(),
Alignment::Right => v_flex().items_end(),
};
let cell = container
.w_full()
.child(contents)
.px_2()
.py_1()
.border_color(self.border_color);
let cell = match self.state {
TableState::Header => cell.border_2(),
TableState::Body => cell.border_1(),
};
self.current_row.push(cell);
}
fn finish(self) -> Div {
let mut table = v_flex().w_full();
let mut header = h_flex();
for cell in self.header {
header = header.child(cell);
}
table = table.child(header);
for row in self.body {
let mut row_div = h_flex();
for cell in row {
row_div = row_div.child(cell);
}
table = table.child(row_div);
}
table
}
fn alignment_for_next_cell(&self) -> Alignment {
self.column_alignments
.get(self.current_row.len())
.copied()
.unwrap_or(Alignment::None)
}
}
struct Renderer<I> {
source_contents: String,
iter: I,
theme: Arc<Theme>,
finished: Vec<Div>,
language_registry: Arc<LanguageRegistry>,
table: Option<MarkdownTable>,
list_depth: usize,
block_quote_depth: usize,
}
impl<'a, I> Renderer<I>
where
I: Iterator<Item = (Event<'a>, Range<usize>)>,
{
fn new(
iter: I,
source_contents: String,
language_registry: &Arc<LanguageRegistry>,
theme: Arc<Theme>,
) -> Self {
Self {
iter,
source_contents,
theme,
table: None,
finished: vec![],
language_registry: language_registry.clone(),
list_depth: 0,
block_quote_depth: 0,
}
}
fn run(mut self, cx: &WindowContext) -> Self {
while let Some((event, source_range)) = self.iter.next() {
match event {
Event::Start(tag) => {
self.start_tag(tag);
}
Event::End(tag) => {
self.end_tag(tag, source_range, cx);
}
Event::Rule => {
let rule = div().w_full().h(px(2.)).bg(self.theme.colors().border);
self.finished.push(div().mb_4().child(rule));
}
_ => {}
}
}
self
}
fn start_tag(&mut self, tag: Tag<'a>) {
match tag {
Tag::List(_) => {
self.list_depth += 1;
}
Tag::BlockQuote => {
self.block_quote_depth += 1;
}
Tag::Table(column_alignments) => {
self.table = Some(MarkdownTable::new(
self.theme.colors().border,
column_alignments,
));
}
_ => {}
}
}
fn end_tag(&mut self, tag: Tag, source_range: Range<usize>, cx: &WindowContext) {
match tag {
Tag::Paragraph => {
if self.list_depth > 0 || self.block_quote_depth > 0 {
return;
}
let element = self.render_md_from_range(source_range.clone(), cx);
let paragraph = div().mb_3().child(element);
self.finished.push(paragraph);
}
Tag::Heading(level, _, _) => {
let mut headline = self.headline(level);
if source_range.start > 0 {
headline = headline.mt_4();
}
let element = self.render_md_from_range(source_range.clone(), cx);
let headline = headline.child(element);
self.finished.push(headline);
}
Tag::List(_) => {
if self.list_depth == 1 {
let element = self.render_md_from_range(source_range.clone(), cx);
let list = div().mb_3().child(element);
self.finished.push(list);
}
self.list_depth -= 1;
}
Tag::BlockQuote => {
let element = self.render_md_from_range(source_range.clone(), cx);
let block_quote = h_flex()
.mb_3()
.child(
div()
.w(px(4.))
.bg(self.theme.colors().border)
.h_full()
.mr_2()
.mt_1(),
)
.text_color(self.theme.colors().text_muted)
.child(element);
self.finished.push(block_quote);
self.block_quote_depth -= 1;
}
Tag::CodeBlock(kind) => {
let contents = self.source_contents[source_range.clone()].trim();
let contents = contents.trim_start_matches("```");
let contents = contents.trim_end_matches("```");
let contents = match kind {
CodeBlockKind::Fenced(language) => {
contents.trim_start_matches(&language.to_string())
}
CodeBlockKind::Indented => contents,
};
let contents: String = contents.into();
let contents = SharedString::from(contents);
let code_block = div()
.mb_3()
.px_4()
.py_0()
.bg(self.theme.colors().surface_background)
.child(StyledText::new(contents));
self.finished.push(code_block);
}
Tag::Table(_alignment) => {
if self.table.is_none() {
log::error!("Table end without table ({:?})", source_range);
return;
}
let table = self.table.take().unwrap();
let table = table.finish().mb_4();
self.finished.push(table);
}
Tag::TableHead => {
if self.table.is_none() {
log::error!("Table head without table ({:?})", source_range);
return;
}
self.table.as_mut().unwrap().finish_row();
}
Tag::TableRow => {
if self.table.is_none() {
log::error!("Table row without table ({:?})", source_range);
return;
}
self.table.as_mut().unwrap().finish_row();
}
Tag::TableCell => {
if self.table.is_none() {
log::error!("Table cell without table ({:?})", source_range);
return;
}
let contents = self.render_md_from_range(source_range.clone(), cx);
self.table.as_mut().unwrap().add_cell(contents);
}
_ => {}
}
}
fn render_md_from_range(
&self,
source_range: Range<usize>,
cx: &WindowContext,
) -> gpui::AnyElement {
let mentions = &[];
let language = None;
let paragraph = &self.source_contents[source_range.clone()];
let rich_text = render_rich_text(
paragraph.into(),
mentions,
&self.language_registry,
language,
);
let id: ElementId = source_range.start.into();
rich_text.element(id, cx)
}
fn headline(&self, level: HeadingLevel) -> Div {
let size = match level {
HeadingLevel::H1 => rems(2.),
HeadingLevel::H2 => rems(1.5),
HeadingLevel::H3 => rems(1.25),
HeadingLevel::H4 => rems(1.),
HeadingLevel::H5 => rems(0.875),
HeadingLevel::H6 => rems(0.85),
};
let color = match level {
HeadingLevel::H6 => self.theme.colors().text_muted,
_ => self.theme.colors().text,
};
let line_height = DefiniteLength::from(rems(1.25));
let headline = h_flex()
.w_full()
.line_height(line_height)
.text_size(size)
.text_color(color)
.mb_4()
.pb(rems(0.15));
headline
}
}
pub fn render_markdown(
markdown_input: &str,
language_registry: &Arc<LanguageRegistry>,
cx: &WindowContext,
) -> Vec<Div> {
let theme = cx.theme().clone();
let options = Options::all();
let parser = Parser::new_ext(markdown_input, options);
let renderer = Renderer::new(
parser.into_offset_iter(),
markdown_input.to_owned(),
language_registry,
theme,
);
let renderer = renderer.run(cx);
return renderer.finished;
}

View File

@@ -39,7 +39,7 @@ lsp = { path = "../lsp" }
ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand.workspace = true
rich_text = { path = "../rich_text" }
schemars.workspace = true

View File

@@ -195,11 +195,11 @@ impl Prettier {
},
Path::new("/"),
None,
cx.clone(),
cx,
)
.context("prettier server creation")?;
let server = cx
.update(|cx| executor.spawn(server.initialize(None, cx)))?
let server = executor
.spawn(server.initialize(None))
.await
.context("prettier server initialization")?;
Ok(Self::Real(RealPrettier {

View File

@@ -75,7 +75,6 @@ fs = { path = "../fs", features = ["test-support"] }
git2.workspace = true
gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
release_channel = { path = "../release_channel" }
lsp = { path = "../lsp", features = ["test-support"] }
prettier = { path = "../prettier", features = ["test-support"] }
pretty_assertions.workspace = true

View File

@@ -3130,9 +3130,7 @@ impl Project {
(None, override_options) => initialization_options = override_options,
_ => {}
}
let language_server = cx
.update(|cx| language_server.initialize(initialization_options, cx))?
.await?;
let language_server = language_server.initialize(initialization_options).await?;
language_server
.notify::<lsp::notification::DidChangeConfiguration>(

View File

@@ -4380,7 +4380,6 @@ fn init_test(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
release_channel::init("0.0.0", cx);
language::init(cx);
Project::init_settings(cx);
});

View File

@@ -33,7 +33,6 @@ gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
release_channel = { path = "../release_channel" }
settings = { path = "../settings", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }

View File

@@ -392,7 +392,6 @@ mod tests {
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx);
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);

View File

@@ -1,9 +1,9 @@
use gpui::{AppContext, Global, SemanticVersion};
use gpui::{AppContext, Global};
use once_cell::sync::Lazy;
use std::env;
#[doc(hidden)]
static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
Lazy::new(|| {
env::var("ZED_RELEASE_CHANNEL")
.unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
@@ -11,7 +11,6 @@ static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
} else {
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
};
#[doc(hidden)]
pub static RELEASE_CHANNEL: Lazy<ReleaseChannel> =
Lazy::new(|| match RELEASE_CHANNEL_NAME.as_str() {
@@ -40,29 +39,6 @@ impl AppCommitSha {
}
}
struct GlobalAppVersion(SemanticVersion);
impl Global for GlobalAppVersion {}
pub struct AppVersion;
impl AppVersion {
pub fn init(pkg_version: &str, cx: &mut AppContext) {
let version = if let Some(from_env) = env::var("ZED_APP_VERSION").ok() {
from_env.parse().expect("invalid ZED_APP_VERSION")
} else {
cx.app_metadata()
.app_version
.unwrap_or_else(|| pkg_version.parse().expect("invalid version in Cargo.toml"))
};
cx.set_global(GlobalAppVersion(version))
}
pub fn global(cx: &AppContext) -> SemanticVersion {
cx.global::<GlobalAppVersion>().0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum ReleaseChannel {
#[default]
@@ -76,12 +52,11 @@ struct GlobalReleaseChannel(ReleaseChannel);
impl Global for GlobalReleaseChannel {}
pub fn init(pkg_version: &str, cx: &mut AppContext) {
AppVersion::init(pkg_version, cx);
cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
}
impl ReleaseChannel {
pub fn init(cx: &mut AppContext) {
cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
}
pub fn global(cx: &AppContext) -> Self {
cx.global::<GlobalReleaseChannel>().0
}

View File

@@ -22,7 +22,7 @@ futures.workspace = true
gpui = { path = "../gpui" }
language = { path = "../language" }
lazy_static.workspace = true
pulldown-cmark.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
smallvec.workspace = true
smol.workspace = true
sum_tree = { path = "../sum_tree" }

View File

@@ -47,7 +47,7 @@ pub struct Mention {
}
impl RichText {
pub fn element(&self, id: ElementId, cx: &WindowContext) -> AnyElement {
pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement {
let theme = cx.theme();
let code_background = theme.colors().surface_background;
@@ -83,12 +83,7 @@ impl RichText {
)
.on_click(self.link_ranges.clone(), {
let link_urls = self.link_urls.clone();
move |ix, cx| {
let url = &link_urls[ix];
if url.starts_with("http") {
cx.open_url(url);
}
}
move |ix, cx| cx.open_url(&link_urls[ix])
})
.tooltip({
let link_ranges = self.link_ranges.clone();
@@ -261,7 +256,7 @@ pub fn render_markdown_mut(
}
}
pub fn render_rich_text(
pub fn render_markdown(
block: String,
mentions: &[Mention],
language_registry: &Arc<LanguageRegistry>,

View File

@@ -210,7 +210,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
.get(&*release_channel::RELEASE_CHANNEL.dev_name())
.get(&*release_channel::RELEASE_CHANNEL_NAME)
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)
@@ -543,7 +543,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
.get(&*release_channel::RELEASE_CHANNEL.dev_name())
.get(&*release_channel::RELEASE_CHANNEL_NAME)
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)

View File

@@ -255,23 +255,19 @@ impl ThemeRegistry {
continue;
};
self.load_user_theme(&theme_path, fs.clone())
.await
.log_err();
let Some(reader) = fs.open_sync(&theme_path).await.log_err() else {
continue;
};
let Some(theme) = serde_json_lenient::from_reader(reader).log_err() else {
continue;
};
self.insert_user_theme_families([theme]);
}
Ok(())
}
/// Loads the user theme from the specified path and adds it to the registry.
pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
let reader = fs.open_sync(&theme_path).await?;
let theme = serde_json_lenient::from_reader(reader)?;
self.insert_user_theme_families([theme]);
Ok(())
}
}
impl Default for ThemeRegistry {

View File

@@ -68,6 +68,6 @@ pub async fn latest_github_release(
releases
.into_iter()
.find(|release| !release.assets.is_empty() && release.pre_release == pre_release)
.find(|release| release.pre_release == pre_release)
.ok_or(anyhow!("Failed to find a release"))
}

View File

@@ -1,4 +1,3 @@
use crate::http_proxy_from_env;
pub use anyhow::{anyhow, Result};
use futures::future::BoxFuture;
use isahc::config::{Configurable, RedirectPolicy};
@@ -44,7 +43,6 @@ pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> {
isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(http_proxy_from_env())
.build()
.unwrap(),
),
@@ -97,7 +95,6 @@ pub fn client() -> Arc<dyn HttpClient> {
isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(http_proxy_from_env())
.build()
.unwrap(),
)

View File

@@ -1,46 +0,0 @@
use std::{
fmt::{self, Display},
str::FromStr,
};
use anyhow::{anyhow, Result};
use serde::Serialize;
/// A datastructure representing a semantic version number
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SemanticVersion {
pub major: usize,
pub minor: usize,
pub patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

View File

@@ -3,7 +3,6 @@ pub mod fs;
pub mod github;
pub mod http;
pub mod paths;
mod semantic_version;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@@ -11,7 +10,6 @@ pub use backtrace::Backtrace;
use futures::Future;
use lazy_static::lazy_static;
use rand::{seq::SliceRandom, Rng};
pub use semantic_version::SemanticVersion;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@@ -44,28 +42,6 @@ pub fn truncate(s: &str, max_chars: usize) -> &str {
}
}
pub fn http_proxy_from_env() -> 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();
}
)+
};
}
try_env!(
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy"
);
None
}
/// Removes characters from the end of the string if its length is greater than `max_chars` and
/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {

View File

@@ -43,7 +43,6 @@ zed_actions = { path = "../zed_actions" }
editor = { path = "../editor", features = ["test-support"] }
futures.workspace = true
gpui = { path = "../gpui", features = ["test-support"] }
release_channel = { path = "../release_channel" }
indoc.workspace = true
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }

View File

@@ -23,7 +23,6 @@ impl VimTestContext {
search::init(cx);
let settings = SettingsStore::test(cx);
cx.set_global(settings);
release_channel::init("0.0.0", cx);
command_palette::init(cx);
crate::init(cx);
});

View File

@@ -204,7 +204,8 @@ impl Vim {
let editor = editor.read(cx);
if editor.leader_peer_id().is_none() {
let newest = editor.selections.newest::<usize>(cx);
local_selections_changed(newest, cx);
let is_multicursor = editor.selections.count() > 1;
local_selections_changed(newest, is_multicursor, cx);
}
}
EditorEvent::InputIgnored { text } => {
@@ -626,13 +627,24 @@ impl Settings for VimModeSetting {
}
}
fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
fn local_selections_changed(
newest: Selection<usize>,
is_multicursor: bool,
cx: &mut WindowContext,
) {
Vim::update(cx, |vim, cx| {
if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
vim.switch_mode(Mode::VisualBlock, false, cx);
} else {
vim.switch_mode(Mode::Visual, false, cx)
if vim.enabled {
if vim.state().mode == Mode::Normal && !newest.is_empty() {
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
vim.switch_mode(Mode::VisualBlock, false, cx);
} else {
vim.switch_mode(Mode::Visual, false, cx)
}
} else if newest.is_empty()
&& !is_multicursor
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode)
{
vim.switch_mode(Mode::Normal, true, cx)
}
}
})

View File

@@ -3,9 +3,8 @@ use crate::DraggedDock;
use crate::{status_bar::StatusItemView, Workspace};
use gpui::{
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement,
Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
WindowContext,
EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -535,18 +534,10 @@ impl Dock {
DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
}
}
fn dispatch_context() -> KeyContext {
let mut dispatch_context = KeyContext::default();
dispatch_context.add("Dock");
dispatch_context
}
}
impl Render for Dock {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let dispatch_context = Self::dispatch_context();
if let Some(entry) = self.visible_entry() {
let size = entry.panel.size(cx);
@@ -597,7 +588,6 @@ impl Render for Dock {
}
div()
.key_context(dispatch_context)
.track_focus(&self.focus_handle)
.flex()
.bg(cx.theme().colors().panel_background)
@@ -622,9 +612,7 @@ impl Render for Dock {
)
.child(handle)
} else {
div()
.key_context(dispatch_context)
.track_focus(&self.focus_handle)
div().track_focus(&self.focus_handle)
}
}
}

View File

@@ -522,17 +522,6 @@ pub enum SplitDirection {
Right,
}
impl std::fmt::Display for SplitDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SplitDirection::Up => write!(f, "up"),
SplitDirection::Down => write!(f, "down"),
SplitDirection::Left => write!(f, "left"),
SplitDirection::Right => write!(f, "right"),
}
}
}
impl SplitDirection {
pub fn all() -> [Self; 4] {
[Self::Up, Self::Down, Self::Left, Self::Right]

View File

@@ -2075,99 +2075,30 @@ impl Workspace {
direction: SplitDirection,
cx: &mut WindowContext,
) {
use ActivateInDirectionTarget as Target;
enum Origin {
LeftDock,
RightDock,
BottomDock,
Center,
if let Some(pane) = self.find_pane_in_direction(direction, cx) {
cx.focus_view(pane);
}
}
let origin: Origin = [
(&self.left_dock, Origin::LeftDock),
(&self.right_dock, Origin::RightDock),
(&self.bottom_dock, Origin::BottomDock),
]
.into_iter()
.find_map(|(dock, origin)| {
if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
Some(origin)
} else {
None
}
})
.unwrap_or(Origin::Center);
let get_last_active_pane = || {
self.last_active_center_pane.as_ref().and_then(|p| {
let p = p.upgrade()?;
(p.read(cx).items_len() != 0).then_some(p)
})
};
let try_dock =
|dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
let target = match (origin, direction) {
// We're in the center, so we first try to go to a different pane,
// otherwise try to go to a dock.
(Origin::Center, direction) => {
if let Some(pane) = self.find_pane_in_direction(direction, cx) {
Some(Target::Pane(pane))
} else {
match direction {
SplitDirection::Up => None,
SplitDirection::Down => try_dock(&self.bottom_dock),
SplitDirection::Left => try_dock(&self.left_dock),
SplitDirection::Right => try_dock(&self.right_dock),
}
}
}
(Origin::LeftDock, SplitDirection::Right) => {
if let Some(last_active_pane) = get_last_active_pane() {
Some(Target::Pane(last_active_pane))
} else {
try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
}
}
(Origin::LeftDock, SplitDirection::Down)
| (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
(Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
(Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
(Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
(Origin::RightDock, SplitDirection::Left) => {
if let Some(last_active_pane) = get_last_active_pane() {
Some(Target::Pane(last_active_pane))
} else {
try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
}
}
_ => None,
};
match target {
Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
Some(ActivateInDirectionTarget::Dock(dock)) => {
if let Some(panel) = dock.read(cx).active_panel() {
panel.focus_handle(cx).focus(cx);
} else {
log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
}
}
None => {}
pub fn swap_pane_in_direction(
&mut self,
direction: SplitDirection,
cx: &mut ViewContext<Self>,
) {
if let Some(to) = self
.find_pane_in_direction(direction, cx)
.map(|pane| pane.clone())
{
self.center.swap(&self.active_pane.clone(), &to);
cx.notify();
}
}
fn find_pane_in_direction(
&mut self,
direction: SplitDirection,
cx: &WindowContext,
) -> Option<View<Pane>> {
cx: &AppContext,
) -> Option<&View<Pane>> {
let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
return None;
};
@@ -2193,21 +2124,7 @@ impl Workspace {
Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
}
};
self.center.pane_at_pixel_position(target).cloned()
}
pub fn swap_pane_in_direction(
&mut self,
direction: SplitDirection,
cx: &mut ViewContext<Self>,
) {
if let Some(to) = self
.find_pane_in_direction(direction, cx)
.map(|pane| pane.clone())
{
self.center.swap(&self.active_pane.clone(), &to);
cx.notify();
}
self.center.pane_at_pixel_position(target)
}
fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
@@ -3571,11 +3488,6 @@ fn open_items(
})
}
enum ActivateInDirectionTarget {
Pane(View<Pane>),
Dock(View<Dock>),
}
fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";

View File

@@ -65,7 +65,6 @@ lazy_static.workspace = true
libc = "0.2"
log.workspace = true
lsp = { path = "../lsp" }
markdown_preview = { path = "../markdown_preview" }
menu = { path = "../menu" }
mimalloc = "0.1"
node_runtime = { path = "../node_runtime" }
@@ -114,7 +113,6 @@ tree-sitter-css.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-elm.workspace = true
tree-sitter-embedded-template.workspace = true
tree-sitter-erlang.workspace = true
tree-sitter-gitcommit.workspace = true
tree-sitter-gleam.workspace = true
tree-sitter-glsl.workspace = true

View File

@@ -15,7 +15,6 @@ mod css;
mod deno;
mod elixir;
mod elm;
mod erlang;
mod gleam;
mod go;
mod haskell;
@@ -114,12 +113,6 @@ pub fn init(
),
}
language("gitcommit", tree_sitter_gitcommit::language(), vec![]);
language(
"erlang",
tree_sitter_erlang::language(),
vec![Arc::new(erlang::ErlangLspAdapter)],
);
language(
"gleam",
tree_sitter_gleam::language(),

View File

@@ -1,58 +0,0 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf};
pub struct ErlangLspAdapter;
#[async_trait]
impl LspAdapter for ErlangLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("erlang_ls".into())
}
fn short_name(&self) -> &'static str {
"erlang_ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(()) as Box<_>)
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"erlang_ls must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "erlang_ls".into(),
arguments: vec![],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "erlang_ls".into(),
arguments: vec!["--version".into()],
})
}
}

View File

@@ -1,3 +0,0 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)

View File

@@ -1,23 +0,0 @@
name = "Erlang"
# TODO: support parsing rebar.config files
# # https://github.com/WhatsApp/tree-sitter-erlang/issues/3
path_suffixes = ["erl", "hrl", "app.src", "escript", "xrl", "yrl", "Emakefile", "rebar.config"]
line_comments = ["% ", "%% ", "%%% "]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "<<", end = ">>", close = true, newline = false, not_in = ["string"] },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
# Indent if a line ends brackets, "->" or most keywords. Also if prefixed
# with "||". This should work with most formatting models.
# The ([^%]).* is to ensure this doesn't match inside comments.
increase_indent_pattern = "^([^%]).*([{(\\[]]|\\->|after|begin|case|catch|fun|if|of|try|when|maybe|else|(\\|\\|.*))\\s*$"
# Dedent after brackets, end or lone "->". The latter happens in a spec
# with indented types, typically after "when". Only do this if it's _only_
# preceded by whitespace.
decrease_indent_pattern = "^\\s*([)}\\]]|end|else|\\->\\s*$)"

View File

@@ -1,9 +0,0 @@
[
(fun_decl)
(anonymous_fun)
(case_expr)
(maybe_expr)
(map_expr)
(export_attribute)
(export_type_attribute)
] @fold

View File

@@ -1,231 +0,0 @@
;; Copyright (c) Facebook, Inc. and its affiliates.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
;; ---------------------------------------------------------------------
;; Based initially on the contents of https://github.com/WhatsApp/tree-sitter-erlang/issues/2 by @Wilfred
;; and https://github.com/the-mikedavis/tree-sitter-erlang/blob/main/queries/highlights.scm
;;
;; The tests are also based on those in
;; https://github.com/the-mikedavis/tree-sitter-erlang/tree/main/test/highlight
;;
;; First match wins in this file
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Attributes
;; module attribute
(module_attribute
name: (atom) @module)
;; behaviour
(behaviour_attribute name: (atom) @module)
;; export
;; Import attribute
(import_attribute
module: (atom) @module)
;; export_type
;; optional_callbacks
;; compile
(compile_options_attribute
options: (tuple
expr: (atom)
expr: (list
exprs: (binary_op_expr
lhs: (atom)
rhs: (integer)))))
;; file attribute
;; record
(record_decl name: (atom) @type)
(record_decl name: (macro_call_expr name: (var) @constant))
(record_field name: (atom) @property)
;; type alias
;; opaque
;; Spec attribute
(spec fun: (atom) @function)
(spec
module: (module name: (atom) @module)
fun: (atom) @function)
;; callback
(callback fun: (atom) @function)
;; fun decl
;; include/include_lib
;; ifdef/ifndef
(pp_ifdef name: (_) @keyword.directive)
(pp_ifndef name: (_) @keyword.directive)
;; define
(pp_define
lhs: (macro_lhs
name: (_) @keyword.directive
args: (var_args args: (var))))
(pp_define
lhs: (macro_lhs
name: (var) @constant))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions
(fa fun: (atom) @function)
(type_name name: (atom) @function)
(call expr: (atom) @function)
(function_clause name: (atom) @function)
(internal_fun fun: (atom) @function)
;; This is a fudge, we should check that the operator is '/'
;; But our grammar does not (currently) provide it
(binary_op_expr lhs: (atom) @function rhs: (integer))
;; Others
(remote_module module: (atom) @module)
(remote fun: (atom) @function)
(macro_call_expr name: (var) @keyword.directive args: (_) )
(macro_call_expr name: (var) @constant)
(macro_call_expr name: (atom) @keyword.directive)
(record_field_name name: (atom) @property)
(record_name name: (atom) @type)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Reserved words
[ "after"
"and"
"band"
"begin"
"behavior"
"behaviour"
"bnot"
"bor"
"bsl"
"bsr"
"bxor"
"callback"
"case"
"catch"
"compile"
"define"
"deprecated"
"div"
"elif"
"else"
"end"
"endif"
"export"
"export_type"
"file"
"fun"
"if"
"ifdef"
"ifndef"
"import"
"include"
"include_lib"
"maybe"
"module"
"of"
"opaque"
"optional_callbacks"
"or"
"receive"
"record"
"spec"
"try"
"type"
"undef"
"unit"
"when"
"xor"] @keyword
["andalso" "orelse"] @keyword.operator
;; Punctuation
["," "." ";"] @punctuation.delimiter
["(" ")" "{" "}" "[" "]" "<<" ">>"] @punctuation.bracket
;; Operators
["!"
"->"
"<-"
"#"
"::"
"|"
":"
"="
"||"
"+"
"-"
"bnot"
"not"
"/"
"*"
"div"
"rem"
"band"
"and"
"+"
"-"
"bor"
"bxor"
"bsl"
"bsr"
"or"
"xor"
"++"
"--"
"=="
"/="
"=<"
"<"
">="
">"
"=:="
"=/="
] @operator
;;; Comments
((var) @comment.discard
(#match? @comment.discard "^_"))
(dotdotdot) @comment.discard
(comment) @comment
;; Primitive types
(string) @string
(char) @constant
(integer) @number
(var) @variable
(atom) @string.special.symbol
;; wild attribute (Should take precedence over atoms, otherwise they are highlighted as atoms)
(wild_attribute name: (attr_name name: (_) @keyword))

View File

@@ -1,3 +0,0 @@
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View File

@@ -1,31 +0,0 @@
(module_attribute
"module" @context
name: (_) @name) @item
(behaviour_attribute
"behaviour" @context
(atom) @name) @item
(type_alias
"type" @context
name: (_) @name) @item
(opaque
"opaque" @context
name: (_) @name) @item
(pp_define
"define" @context
lhs: (_) @name) @item
(record_decl
"record" @context
name: (_) @name) @item
(callback
"callback" @context
fun: (_) @function ( (_) @name)) @item
(fun_decl (function_clause
name: (_) @name
args: (_) @context)) @item

View File

@@ -1,4 +1,4 @@
name = "Git Commit"
name = "Git commit"
path_suffixes = [
# Refer to https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua#L1286-L1290
"TAG_EDITMSG",

View File

@@ -11,7 +11,6 @@ use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use env_logger::Builder;
use fs::RealFs;
use fsevent::StreamFlags;
use futures::StreamExt;
use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
use isahc::{prelude::Configurable, Request};
@@ -121,7 +120,7 @@ fn main() {
});
app.run(move |cx| {
release_channel::init(env!("CARGO_PKG_VERSION"), cx);
ReleaseChannel::init(cx);
if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
}
@@ -172,8 +171,35 @@ fn main() {
);
assistant::init(cx);
load_user_themes_in_background(fs.clone(), cx);
watch_themes(fs.clone(), cx);
// TODO: Should we be loading the themes in a different spot?
cx.spawn({
let fs = fs.clone();
|cx| async move {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
if let Some(_theme) =
theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
})
.detach();
cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
.detach();
@@ -248,7 +274,6 @@ fn main() {
notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
collab_ui::init(&app_state, cx);
feedback::init(cx);
markdown_preview::init(cx);
welcome::init(cx);
cx.set_menus(app_menus());
@@ -583,13 +608,9 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
std::process::exit(-1);
}
let app_version = if let Some(version) = app_metadata.app_version {
version.to_string()
} else {
option_env!("CARGO_PKG_VERSION")
.unwrap_or("dev")
.to_string()
};
let app_version = client::ZED_APP_VERSION
.or(app_metadata.app_version)
.map_or("dev".to_string(), |v| v.to_string());
let backtrace = Backtrace::new();
let mut backtrace = backtrace
@@ -618,7 +639,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
file: location.file().into(),
line: location.line(),
}),
app_version: app_version.to_string(),
app_version: app_version.clone(),
release_channel: RELEASE_CHANNEL.display_name().into(),
os_name: app_metadata.os_name.into(),
os_version: app_metadata
@@ -878,81 +899,6 @@ fn load_embedded_fonts(cx: &AppContext) {
.unwrap();
}
/// Spawns a background task to load the user themes from the themes directory.
fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
cx.spawn({
let fs = fs.clone();
|cx| async move {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
})
.detach();
}
/// Spawns a background task to watch the themes directory for changes.
fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
cx.spawn(|cx| async move {
let mut events = fs
.watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100))
.await;
while let Some(events) = events.next().await {
for event in events {
if event.flags.contains(StreamFlags::ITEM_REMOVED) {
// Theme was removed, don't need to reload.
// We may want to remove the theme from the registry, in this case.
} else {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_theme(&event.path, fs.clone())
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) =
theme_settings.requested_theme.clone()
{
if let Some(_theme) =
theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
}
}
})
.detach()
}
async fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>) {
let reload_debounce = Duration::from_millis(250);

View File

@@ -50,7 +50,7 @@ Finally, Vim mode's search and replace functionality is backed by Zed's. This me
## Custom key bindings
Zed does not yet have an equivalent to vims `map` command to convert one set of keystrokes into another, however you can bind any sequence of keys to fire any Action documented in the [Key bindings documentation](https://zed.dev/docs/key-bindings).
Zed does not yet have an equivalent to vims `map` command to convert one set of keystrokes into another, however you can bind any sequence of keys to fire any Action documented in the [Key bindings documentation](https://docs.zed.dev/configuration/key-bindings).
You can edit your personal key bindings with `:keymap`.
For vim-specific shortcuts, you may find the following template a good place to start:

View File

@@ -1,4 +0,0 @@
# Erlang
- Tree Sitter: [tree-sitter-erlang](https://github.com/WhatsApp/tree-sitter-erlang)
- Language Server: [erlang_ls](https://github.com/erlang-ls/erlang_ls)

View File

@@ -1,4 +0,0 @@
# Git Commit
- Tree Sitter: [tree-sitter-gitcommit](https://github.com/gbprod/tree-sitter-gitcommit)
- Language Server: N/A

View File

@@ -2,18 +2,3 @@
- Tree Sitter: [tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go)
- Language Server: [gopls](https://github.com/golang/tools/tree/master/gopls)
# Go Mod
- Tree Sitter: [tree-sitter-gomod](https://github.com/camdencheek/tree-sitter-go-mod)
- Language Server: N/A
# Go Sum
TODO: https://github.com/zed-industries/zed/pull/7139
# Go Work
- Tree Sitter:
[tree-sitter-go-work](https://github.com/d1y/tree-sitter-go-work)
- Language Server: N/A

8
script/bump-collab-version Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
if [[ $# < 1 ]]; then
echo "Missing version increment (major, minor, or patch)" >&2
exit 1
fi
exec script/lib/bump-version.sh collab collab-v '' $1

View File

@@ -79,11 +79,6 @@ version_info=$(rustc --version --verbose)
host_line=$(echo "$version_info" | grep host)
local_target_triple=${host_line#*: }
if [ -z "$ZED_CLIENT_CHECKSUM_SEED" ]; then
echo "Missing ZED_CLIENT_CHECKSUM_SEED environment variable"
exit 1
fi
if [ "$local_arch" = true ]; then
echo "Building for local target only."
cargo build ${build_flag} --package ${zed_crate}

View File

@@ -3,19 +3,22 @@
set -eu
source script/lib/deploy-helpers.sh
if [[ $# != 1 ]]; then
echo "Usage: $0 <production|staging>"
if [[ $# < 2 ]]; then
echo "Usage: $0 <production|staging> <tag-name>"
exit 1
fi
environment=$1
tag="$(tag_for_environment $environment)"
version=$2
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" != "main" ]; then
echo "You must be on main to run this script"
exit 1
fi
export_vars_for_environment ${environment}
image_id=$(image_id_for_version ${version})
git pull --ff-only origin main
git tag -f $tag
git push -f origin $tag
export ZED_DO_CERTIFICATE_ID=$(doctl compute certificate list --format ID --no-header)
export ZED_KUBE_NAMESPACE=${environment}
export ZED_IMAGE_ID=${image_id}
target_zed_kube_cluster
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
kubectl -n "$environment" rollout status deployment/collab --watch
echo "deployed collab v${version} to ${environment}"

View File

@@ -8,30 +8,33 @@ function export_vars_for_environment {
export $(cat $env_file)
}
function image_id_for_version {
local version=$1
# Check that version is valid
if [[ ! ${version} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version number '${version}'" >&2
exit 1
fi
# Check that image exists for version
tag_names=$(doctl registry repository list-tags collab --no-header --format Tag)
if ! $(echo "${tag_names}" | grep -Fqx v${version}); then
echo "No docker image tagged for version '${version}'" >&2
echo "Found images with these tags:" ${tag_names} >&2
exit 1
fi
echo "registry.digitalocean.com/zed/collab:v${version}"
}
function version_for_image_id {
local image_id=$1
echo $image_id | cut -d: -f2
}
function target_zed_kube_cluster {
if [[ $(kubectl config current-context 2> /dev/null) != do-nyc1-zed-1 ]]; then
doctl kubernetes cluster kubeconfig save zed-1
fi
}
function tag_for_environment {
if [[ "$1" == "production" ]]; then
echo "collab-production"
elif [[ "$1" == "staging" ]]; then
echo "collab-staging"
else
echo "Invalid environment name '${environment}'" >&2
exit 1
fi
}
function url_for_environment {
if [[ "$1" == "production" ]]; then
echo "https://collab.zed.dev"
elif [[ "$1" == "staging" ]]; then
echo "https://collab-staging.zed.dev"
else
echo "Invalid environment name '${environment}'" >&2
exit 1
fi
}

View File

@@ -3,15 +3,13 @@
set -eu
source script/lib/deploy-helpers.sh
if [[ $# != 1 ]]; then
if [[ $# < 1 ]]; then
echo "Usage: $0 <production|staging>"
exit 1
fi
environment=$1
url="$(url_for_environment $environment)"
tag="$(tag_for_environment $environment)"
export_vars_for_environment ${environment}
target_zed_kube_cluster
deployed_image_id=$(
@@ -22,9 +20,18 @@ deployed_image_id=$(
| cut -d: -f2
)
echo "Deployed image version: $deployed_image_id"
job_image_ids=$(
kubectl \
--namespace=${environment} \
get jobs \
-o 'jsonpath={range .items[0:5]}{.spec.template.spec.containers[0].image}{"\n"}{end}' \
2> /dev/null \
|| true
)
git fetch >/dev/null
if [[ "$(git rev-parse tags/$tag)" != $deployed_image_id ]]; then
echo "NOTE: tags/$tag $(git rev-parse tags/$tag) is not yet deployed"
fi;
echo "Deployed image version:"
echo "$deployed_image_id"
echo
echo "Migration job image versions:"
echo "$job_image_ids"
echo