Compare commits
1 Commits
visual-key
...
thorsten-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3647c2e351 |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -14,7 +14,6 @@ on:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- ".github/workflows/community_*"
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
@@ -267,20 +266,20 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (universal) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -331,7 +330,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -378,7 +377,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
|
||||
|
||||
Thanks for your help!
|
||||
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
|
||||
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, feel free to ping a Zed team member to reopen this issue or open a new one."
|
||||
# We will increase `days-before-stale` to 365 on or after Jan 24th,
|
||||
# 2024. This date marks one year since migrating issues from
|
||||
# 'community' to 'zed' repository. The migration added activity to all
|
||||
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -9151,7 +9151,6 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
@@ -9303,7 +9302,6 @@ dependencies = [
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-socks",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
@@ -12002,7 +12000,6 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14742,7 +14739,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_elixir"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -14866,6 +14863,13 @@ dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_svelte"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_terraform"
|
||||
version = "0.1.1"
|
||||
@@ -14894,6 +14898,14 @@ dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_vue"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.3.1"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -158,10 +158,12 @@ members = [
|
||||
"extensions/ruff",
|
||||
"extensions/slash-commands-example",
|
||||
"extensions/snippets",
|
||||
"extensions/svelte",
|
||||
"extensions/terraform",
|
||||
"extensions/test-extension",
|
||||
"extensions/toml",
|
||||
"extensions/uiua",
|
||||
"extensions/vue",
|
||||
"extensions/zig",
|
||||
|
||||
#
|
||||
@@ -389,14 +391,7 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
|
||||
rand = "0.8.5"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
|
||||
"charset",
|
||||
"http2",
|
||||
"macos-system-configuration",
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"stream",
|
||||
] }
|
||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = ["charset", "http2", "macos-system-configuration", "rustls-tls-native-roots", "stream"]}
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.15", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
"php": "php",
|
||||
"plist": "template",
|
||||
"png": "image",
|
||||
"postcss": "css",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierignore": "prettier",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"cmd-]": "pane::GoForward",
|
||||
"alt-f7": "editor::FindAllReferences",
|
||||
"cmd-alt-f7": "editor::FindAllReferences",
|
||||
"cmd-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"cmd-alt-b": "editor::GoToDefinitionSplit",
|
||||
"cmd-shift-b": "editor::GoToTypeDefinition",
|
||||
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
|
||||
@@ -64,8 +64,7 @@
|
||||
"cmd-shift-o": "file_finder::Toggle",
|
||||
"cmd-shift-a": "command_palette::Toggle",
|
||||
"shift shift": "command_palette::Toggle",
|
||||
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
|
||||
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
|
||||
"cmd-alt-o": "project_symbols::Toggle",
|
||||
"cmd-1": "workspace::ToggleLeftDock",
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
}
|
||||
|
||||
@@ -198,6 +198,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_llm_billing_enabled(&self) -> bool {
|
||||
self.stripe_api_key.is_some()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -460,27 +460,29 @@ async fn check_usage_limit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
|
||||
if !claims.has_llm_subscription {
|
||||
return Err(Error::http(
|
||||
StatusCode::PAYMENT_REQUIRED,
|
||||
"Maximum spending limit reached for this month.".to_string(),
|
||||
));
|
||||
}
|
||||
if state.config.is_llm_billing_enabled() {
|
||||
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
|
||||
if !claims.has_llm_subscription {
|
||||
return Err(Error::http(
|
||||
StatusCode::PAYMENT_REQUIRED,
|
||||
"Maximum spending limit reached for this month.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
|
||||
>= Cents(claims.max_monthly_spend_in_cents)
|
||||
{
|
||||
return Err(Error::Http(
|
||||
StatusCode::FORBIDDEN,
|
||||
"Maximum spending limit reached for this month.".to_string(),
|
||||
[(
|
||||
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
|
||||
HeaderValue::from_static("true"),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
));
|
||||
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
|
||||
>= Cents(claims.max_monthly_spend_in_cents)
|
||||
{
|
||||
return Err(Error::Http(
|
||||
StatusCode::FORBIDDEN,
|
||||
"Maximum spending limit reached for this month.".to_string(),
|
||||
[(
|
||||
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
|
||||
HeaderValue::from_static("true"),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,6 +627,7 @@ where
|
||||
impl<S> Drop for TokenCountingStream<S> {
|
||||
fn drop(&mut self) {
|
||||
let state = self.state.clone();
|
||||
let is_llm_billing_enabled = state.config.is_llm_billing_enabled();
|
||||
let claims = self.claims.clone();
|
||||
let provider = self.provider;
|
||||
let model = std::mem::take(&mut self.model);
|
||||
@@ -638,7 +641,14 @@ impl<S> Drop for TokenCountingStream<S> {
|
||||
provider,
|
||||
&model,
|
||||
tokens,
|
||||
claims.has_llm_subscription,
|
||||
// We're passing `false` here if LLM billing is not enabled
|
||||
// so that we don't write any records to the
|
||||
// `billing_events` table until we're ready to bill users.
|
||||
if is_llm_billing_enabled {
|
||||
claims.has_llm_subscription
|
||||
} else {
|
||||
false
|
||||
},
|
||||
Cents(claims.max_monthly_spend_in_cents),
|
||||
Utc::now(),
|
||||
)
|
||||
|
||||
@@ -962,6 +962,7 @@ fn random_diagnostic(
|
||||
|
||||
const FILE_HEADER: &str = "file header";
|
||||
const EXCERPT_HEADER: &str = "excerpt header";
|
||||
const EXCERPT_FOOTER: &str = "excerpt footer";
|
||||
|
||||
fn editor_blocks(
|
||||
editor: &View<Editor>,
|
||||
@@ -997,7 +998,7 @@ fn editor_blocks(
|
||||
.ok()?
|
||||
}
|
||||
|
||||
Block::ExcerptBoundary {
|
||||
Block::ExcerptHeader {
|
||||
starts_new_buffer, ..
|
||||
} => {
|
||||
if *starts_new_buffer {
|
||||
@@ -1006,6 +1007,7 @@ fn editor_blocks(
|
||||
EXCERPT_HEADER.into()
|
||||
}
|
||||
}
|
||||
Block::ExcerptFooter { .. } => EXCERPT_FOOTER.into(),
|
||||
};
|
||||
|
||||
Some((row, name))
|
||||
|
||||
@@ -5,8 +5,8 @@ use super::{
|
||||
use crate::{EditorStyle, GutterDimensions};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -128,17 +128,26 @@ pub struct BlockContext<'a, 'b> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BlockId {
|
||||
Custom(CustomBlockId),
|
||||
ExcerptBoundary(Option<ExcerptId>),
|
||||
ExcerptHeader(ExcerptId),
|
||||
ExcerptFooter(ExcerptId),
|
||||
}
|
||||
|
||||
impl From<BlockId> for EntityId {
|
||||
fn from(value: BlockId) -> Self {
|
||||
match value {
|
||||
BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
|
||||
BlockId::ExcerptHeader(id) => id.into(),
|
||||
BlockId::ExcerptFooter(id) => id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockId> for ElementId {
|
||||
fn from(value: BlockId) -> Self {
|
||||
match value {
|
||||
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
|
||||
BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt {
|
||||
Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(),
|
||||
None => "LastExcerptBoundary".into(),
|
||||
},
|
||||
BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
|
||||
BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +156,8 @@ impl std::fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Custom(id) => write!(f, "Block({id:?})"),
|
||||
Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
|
||||
Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
|
||||
Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +177,8 @@ struct Transform {
|
||||
|
||||
pub(crate) enum BlockType {
|
||||
Custom(CustomBlockId),
|
||||
ExcerptBoundary,
|
||||
Header,
|
||||
Footer,
|
||||
}
|
||||
|
||||
pub(crate) trait BlockLike {
|
||||
@@ -180,20 +191,27 @@ pub(crate) trait BlockLike {
|
||||
#[derive(Clone)]
|
||||
pub enum Block {
|
||||
Custom(Arc<CustomBlock>),
|
||||
ExcerptBoundary {
|
||||
prev_excerpt: Option<ExcerptInfo>,
|
||||
next_excerpt: Option<ExcerptInfo>,
|
||||
ExcerptHeader {
|
||||
id: ExcerptId,
|
||||
buffer: BufferSnapshot,
|
||||
range: ExcerptRange<text::Anchor>,
|
||||
height: u32,
|
||||
starts_new_buffer: bool,
|
||||
show_excerpt_controls: bool,
|
||||
},
|
||||
ExcerptFooter {
|
||||
id: ExcerptId,
|
||||
disposition: BlockDisposition,
|
||||
height: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl BlockLike for Block {
|
||||
fn block_type(&self) -> BlockType {
|
||||
match self {
|
||||
Block::Custom(block) => BlockType::Custom(block.id),
|
||||
Block::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
|
||||
Block::ExcerptHeader { .. } => BlockType::Header,
|
||||
Block::ExcerptFooter { .. } => BlockType::Footer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +222,8 @@ impl BlockLike for Block {
|
||||
fn priority(&self) -> usize {
|
||||
match self {
|
||||
Block::Custom(block) => block.priority,
|
||||
Block::ExcerptBoundary { .. } => usize::MAX,
|
||||
Block::ExcerptHeader { .. } => usize::MAX,
|
||||
Block::ExcerptFooter { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,36 +232,32 @@ impl Block {
|
||||
pub fn id(&self) -> BlockId {
|
||||
match self {
|
||||
Block::Custom(block) => BlockId::Custom(block.id),
|
||||
Block::ExcerptBoundary { next_excerpt, .. } => {
|
||||
BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id))
|
||||
}
|
||||
Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
|
||||
Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
|
||||
}
|
||||
}
|
||||
|
||||
fn disposition(&self) -> BlockDisposition {
|
||||
match self {
|
||||
Block::Custom(block) => block.disposition,
|
||||
Block::ExcerptBoundary { next_excerpt, .. } => {
|
||||
if next_excerpt.is_some() {
|
||||
BlockDisposition::Above
|
||||
} else {
|
||||
BlockDisposition::Below
|
||||
}
|
||||
}
|
||||
Block::ExcerptHeader { .. } => BlockDisposition::Above,
|
||||
Block::ExcerptFooter { disposition, .. } => *disposition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
match self {
|
||||
Block::Custom(block) => block.height,
|
||||
Block::ExcerptBoundary { height, .. } => *height,
|
||||
Block::ExcerptHeader { height, .. } => *height,
|
||||
Block::ExcerptFooter { height, .. } => *height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(&self) -> BlockStyle {
|
||||
match self {
|
||||
Block::Custom(block) => block.style,
|
||||
Block::ExcerptBoundary { .. } => BlockStyle::Sticky,
|
||||
Block::ExcerptHeader { .. } => BlockStyle::Sticky,
|
||||
Block::ExcerptFooter { .. } => BlockStyle::Sticky,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,17 +266,24 @@ impl Debug for Block {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
|
||||
Self::ExcerptBoundary {
|
||||
Self::ExcerptHeader {
|
||||
buffer,
|
||||
starts_new_buffer,
|
||||
next_excerpt,
|
||||
prev_excerpt,
|
||||
id,
|
||||
..
|
||||
} => f
|
||||
.debug_struct("ExcerptBoundary")
|
||||
.field("prev_excerpt", &prev_excerpt)
|
||||
.field("next_excerpt", &next_excerpt)
|
||||
.debug_struct("ExcerptHeader")
|
||||
.field("id", &id)
|
||||
.field("path", &buffer.file().map(|f| f.path()))
|
||||
.field("starts_new_buffer", &starts_new_buffer)
|
||||
.finish(),
|
||||
Block::ExcerptFooter {
|
||||
id, disposition, ..
|
||||
} => f
|
||||
.debug_struct("ExcerptFooter")
|
||||
.field("id", &id)
|
||||
.field("disposition", &disposition)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,62 +595,66 @@ impl BlockMap {
|
||||
{
|
||||
buffer
|
||||
.excerpt_boundaries_in_range(range)
|
||||
.filter_map(move |excerpt_boundary| {
|
||||
let wrap_row;
|
||||
if excerpt_boundary.next.is_some() {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.row();
|
||||
} else {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
excerpt_boundary.row.0,
|
||||
buffer.line_len(excerpt_boundary.row),
|
||||
),
|
||||
Bias::Left,
|
||||
.flat_map(move |excerpt_boundary| {
|
||||
let mut wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.row();
|
||||
|
||||
[
|
||||
show_excerpt_controls
|
||||
.then(|| {
|
||||
let disposition;
|
||||
if excerpt_boundary.next.is_some() {
|
||||
disposition = BlockDisposition::Above;
|
||||
} else {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
excerpt_boundary.row.0,
|
||||
buffer.line_len(excerpt_boundary.row),
|
||||
),
|
||||
Bias::Left,
|
||||
)
|
||||
.row();
|
||||
disposition = BlockDisposition::Below;
|
||||
}
|
||||
|
||||
excerpt_boundary.prev.as_ref().map(|prev| {
|
||||
(
|
||||
wrap_row,
|
||||
Block::ExcerptFooter {
|
||||
id: prev.id,
|
||||
height: excerpt_footer_height,
|
||||
disposition,
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
.flatten(),
|
||||
excerpt_boundary.next.map(|next| {
|
||||
let starts_new_buffer = excerpt_boundary
|
||||
.prev
|
||||
.map_or(true, |prev| prev.buffer_id != next.buffer_id);
|
||||
|
||||
(
|
||||
wrap_row,
|
||||
Block::ExcerptHeader {
|
||||
id: next.id,
|
||||
buffer: next.buffer,
|
||||
range: next.range,
|
||||
height: if starts_new_buffer {
|
||||
buffer_header_height
|
||||
} else {
|
||||
excerpt_header_height
|
||||
},
|
||||
starts_new_buffer,
|
||||
show_excerpt_controls,
|
||||
},
|
||||
)
|
||||
.row();
|
||||
}
|
||||
|
||||
let starts_new_buffer = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
|
||||
(_, None) => false,
|
||||
(None, Some(_)) => true,
|
||||
(Some(prev), Some(next)) => prev.buffer_id != next.buffer_id,
|
||||
};
|
||||
|
||||
let mut height = 0;
|
||||
if excerpt_boundary.prev.is_some() {
|
||||
if show_excerpt_controls {
|
||||
height += excerpt_footer_height;
|
||||
}
|
||||
}
|
||||
if excerpt_boundary.next.is_some() {
|
||||
if starts_new_buffer {
|
||||
height += buffer_header_height;
|
||||
if show_excerpt_controls {
|
||||
height += excerpt_header_height;
|
||||
}
|
||||
} else {
|
||||
height += excerpt_header_height;
|
||||
}
|
||||
}
|
||||
|
||||
if height == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
wrap_row,
|
||||
Block::ExcerptBoundary {
|
||||
prev_excerpt: excerpt_boundary.prev,
|
||||
next_excerpt: excerpt_boundary.next,
|
||||
height,
|
||||
starts_new_buffer,
|
||||
show_excerpt_controls,
|
||||
},
|
||||
))
|
||||
}),
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut [(u32, B)]) {
|
||||
@@ -639,9 +665,12 @@ impl BlockMap {
|
||||
.disposition()
|
||||
.cmp(&block_b.disposition())
|
||||
.then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
|
||||
(BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal,
|
||||
(BlockType::ExcerptBoundary, _) => Ordering::Less,
|
||||
(_, BlockType::ExcerptBoundary) => Ordering::Greater,
|
||||
(BlockType::Footer, BlockType::Footer) => Ordering::Equal,
|
||||
(BlockType::Footer, _) => Ordering::Less,
|
||||
(_, BlockType::Footer) => Ordering::Greater,
|
||||
(BlockType::Header, BlockType::Header) => Ordering::Equal,
|
||||
(BlockType::Header, _) => Ordering::Less,
|
||||
(_, BlockType::Header) => Ordering::Greater,
|
||||
(BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
|
||||
.priority()
|
||||
.cmp(&block_a.priority())
|
||||
@@ -1016,19 +1045,33 @@ impl BlockSnapshot {
|
||||
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
|
||||
Some(Block::Custom(custom_block.clone()))
|
||||
}
|
||||
BlockId::ExcerptBoundary(next_excerpt_id) => {
|
||||
let wrap_point;
|
||||
if let Some(next_excerpt_id) = next_excerpt_id {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?;
|
||||
wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.start, Bias::Left);
|
||||
} else {
|
||||
wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(buffer.max_point(), Bias::Left);
|
||||
BlockId::ExcerptHeader(excerpt_id) => {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.start, Bias::Left);
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.id() == block_id {
|
||||
return Some(block.clone());
|
||||
}
|
||||
} else if cursor.start().0 > WrapRow(wrap_point.row()) {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
BlockId::ExcerptFooter(excerpt_id) => {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.end, Bias::Left);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
@@ -1425,7 +1468,7 @@ mod tests {
|
||||
};
|
||||
use gpui::{div, font, px, AppContext, Context as _, Element};
|
||||
use language::{Buffer, Capability};
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
use std::env;
|
||||
@@ -1681,20 +1724,22 @@ mod tests {
|
||||
// Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
|
||||
assert_eq!(
|
||||
snapshot.text(),
|
||||
"\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
|
||||
"\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
|
||||
);
|
||||
|
||||
let blocks: Vec<_> = snapshot
|
||||
.blocks_in_range(0..u32::MAX)
|
||||
.map(|(row, block)| (row..row + block.height(), block.id()))
|
||||
.map(|(row, block)| (row, block.id()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
blocks,
|
||||
vec![
|
||||
(0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
|
||||
(4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
|
||||
(9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
|
||||
(14..15, BlockId::ExcerptBoundary(None)), // footer
|
||||
(0, BlockId::ExcerptHeader(excerpt_ids[0])),
|
||||
(3, BlockId::ExcerptFooter(excerpt_ids[0])),
|
||||
(4, BlockId::ExcerptHeader(excerpt_ids[1])),
|
||||
(7, BlockId::ExcerptFooter(excerpt_ids[1])),
|
||||
(8, BlockId::ExcerptHeader(excerpt_ids[2])),
|
||||
(11, BlockId::ExcerptFooter(excerpt_ids[2]))
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -2238,10 +2283,13 @@ mod tests {
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ExpectedBlock {
|
||||
ExcerptBoundary {
|
||||
ExcerptHeader {
|
||||
height: u32,
|
||||
starts_new_buffer: bool,
|
||||
is_last: bool,
|
||||
},
|
||||
ExcerptFooter {
|
||||
height: u32,
|
||||
disposition: BlockDisposition,
|
||||
},
|
||||
Custom {
|
||||
disposition: BlockDisposition,
|
||||
@@ -2255,7 +2303,8 @@ mod tests {
|
||||
fn block_type(&self) -> BlockType {
|
||||
match self {
|
||||
ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
|
||||
ExpectedBlock::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
|
||||
ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
|
||||
ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2266,7 +2315,8 @@ mod tests {
|
||||
fn priority(&self) -> usize {
|
||||
match self {
|
||||
ExpectedBlock::Custom { priority, .. } => *priority,
|
||||
ExpectedBlock::ExcerptBoundary { .. } => usize::MAX,
|
||||
ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
|
||||
ExpectedBlock::ExcerptFooter { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2274,21 +2324,17 @@ mod tests {
|
||||
impl ExpectedBlock {
|
||||
fn height(&self) -> u32 {
|
||||
match self {
|
||||
ExpectedBlock::ExcerptBoundary { height, .. } => *height,
|
||||
ExpectedBlock::ExcerptHeader { height, .. } => *height,
|
||||
ExpectedBlock::Custom { height, .. } => *height,
|
||||
ExpectedBlock::ExcerptFooter { height, .. } => *height,
|
||||
}
|
||||
}
|
||||
|
||||
fn disposition(&self) -> BlockDisposition {
|
||||
match self {
|
||||
ExpectedBlock::ExcerptBoundary { is_last, .. } => {
|
||||
if *is_last {
|
||||
BlockDisposition::Below
|
||||
} else {
|
||||
BlockDisposition::Above
|
||||
}
|
||||
}
|
||||
ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
|
||||
ExpectedBlock::Custom { disposition, .. } => *disposition,
|
||||
ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2302,15 +2348,21 @@ mod tests {
|
||||
height: block.height,
|
||||
priority: block.priority,
|
||||
},
|
||||
Block::ExcerptBoundary {
|
||||
Block::ExcerptHeader {
|
||||
height,
|
||||
starts_new_buffer,
|
||||
next_excerpt,
|
||||
..
|
||||
} => ExpectedBlock::ExcerptBoundary {
|
||||
} => ExpectedBlock::ExcerptHeader {
|
||||
height,
|
||||
starts_new_buffer,
|
||||
is_last: next_excerpt.is_none(),
|
||||
},
|
||||
Block::ExcerptFooter {
|
||||
height,
|
||||
disposition,
|
||||
..
|
||||
} => ExpectedBlock::ExcerptFooter {
|
||||
height,
|
||||
disposition,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2328,7 +2380,8 @@ mod tests {
|
||||
fn as_custom(&self) -> Option<&CustomBlock> {
|
||||
match self {
|
||||
Block::Custom(block) => Some(block),
|
||||
Block::ExcerptBoundary { .. } => None,
|
||||
Block::ExcerptHeader { .. } => None,
|
||||
Block::ExcerptFooter { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ use git::blame::GitBlame;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
|
||||
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
|
||||
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UTF16Selection,
|
||||
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
|
||||
WeakFocusHandle, WeakView, WindowContext,
|
||||
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
|
||||
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
|
||||
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
|
||||
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
|
||||
VisualContext, WeakFocusHandle, WeakView, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
@@ -171,7 +171,7 @@ use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSetti
|
||||
use crate::hover_links::find_url;
|
||||
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
|
||||
|
||||
pub const FILE_HEADER_HEIGHT: u32 = 2;
|
||||
pub const FILE_HEADER_HEIGHT: u32 = 1;
|
||||
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
|
||||
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
|
||||
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
|
||||
@@ -640,6 +640,7 @@ pub struct Editor {
|
||||
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
|
||||
tasks_update_task: Option<Task<()>>,
|
||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||
file_header_size: u32,
|
||||
breadcrumb_header: Option<String>,
|
||||
focused_block: Option<FocusedBlock>,
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||
@@ -1845,6 +1846,7 @@ impl Editor {
|
||||
}),
|
||||
merge_adjacent: true,
|
||||
};
|
||||
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
|
||||
let display_map = cx.new_model(|cx| {
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
@@ -1852,7 +1854,7 @@ impl Editor {
|
||||
font_size,
|
||||
None,
|
||||
show_excerpt_controls,
|
||||
FILE_HEADER_HEIGHT,
|
||||
file_header_size,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
|
||||
fold_placeholder,
|
||||
@@ -2036,6 +2038,7 @@ impl Editor {
|
||||
.restore_unsaved_buffers,
|
||||
blame: None,
|
||||
blame_subscription: None,
|
||||
file_header_size,
|
||||
tasks: Default::default(),
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
@@ -12805,7 +12808,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn file_header_size(&self) -> u32 {
|
||||
FILE_HEADER_HEIGHT
|
||||
self.file_header_size
|
||||
}
|
||||
|
||||
pub fn revert(
|
||||
@@ -14117,7 +14120,7 @@ pub fn diagnostic_block_renderer(
|
||||
|
||||
let multi_line_diagnostic = diagnostic.message.contains('\n');
|
||||
|
||||
let buttons = |diagnostic: &Diagnostic| {
|
||||
let buttons = |diagnostic: &Diagnostic, block_id: BlockId| {
|
||||
if multi_line_diagnostic {
|
||||
v_flex()
|
||||
} else {
|
||||
@@ -14125,7 +14128,7 @@ pub fn diagnostic_block_renderer(
|
||||
}
|
||||
.when(allow_closing, |div| {
|
||||
div.children(diagnostic.is_primary.then(|| {
|
||||
IconButton::new("close-block", IconName::XCircle)
|
||||
IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Transparent)
|
||||
@@ -14135,7 +14138,7 @@ pub fn diagnostic_block_renderer(
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
IconButton::new("copy-block", IconName::Copy)
|
||||
IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Transparent)
|
||||
@@ -14150,7 +14153,7 @@ pub fn diagnostic_block_renderer(
|
||||
)
|
||||
};
|
||||
|
||||
let icon_size = buttons(&diagnostic)
|
||||
let icon_size = buttons(&diagnostic, cx.block_id)
|
||||
.into_any_element()
|
||||
.layout_as_root(AvailableSpace::min_size(), cx);
|
||||
|
||||
@@ -14167,7 +14170,7 @@ pub fn diagnostic_block_renderer(
|
||||
.w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
|
||||
.flex_shrink(),
|
||||
)
|
||||
.child(buttons(&diagnostic))
|
||||
.child(buttons(&diagnostic, cx.block_id))
|
||||
.child(div().flex().flex_shrink_0().child(
|
||||
StyledText::new(text_without_backticks.clone()).with_highlights(
|
||||
&text_style,
|
||||
|
||||
@@ -21,8 +21,7 @@ use crate::{
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
|
||||
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -32,7 +31,7 @@ use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
|
||||
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
EntityId, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
||||
@@ -47,7 +46,7 @@ use language::{
|
||||
ChunkRendererContext,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{Anchor, ExcerptId, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow};
|
||||
use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
|
||||
use project::{
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
ProjectPath,
|
||||
@@ -1633,7 +1632,7 @@ impl EditorElement {
|
||||
let mut block_offset = 0;
|
||||
let mut found_excerpt_header = false;
|
||||
for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
|
||||
if matches!(block, Block::ExcerptBoundary { .. }) {
|
||||
if matches!(block, Block::ExcerptHeader { .. }) {
|
||||
found_excerpt_header = true;
|
||||
break;
|
||||
}
|
||||
@@ -1650,7 +1649,7 @@ impl EditorElement {
|
||||
let mut block_height = 0;
|
||||
let mut found_excerpt_header = false;
|
||||
for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
|
||||
if matches!(block, Block::ExcerptBoundary { .. }) {
|
||||
if matches!(block, Block::ExcerptHeader { .. }) {
|
||||
found_excerpt_header = true;
|
||||
}
|
||||
block_height += block.height();
|
||||
@@ -2101,14 +2100,23 @@ impl EditorElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
Block::ExcerptBoundary {
|
||||
prev_excerpt,
|
||||
next_excerpt,
|
||||
show_excerpt_controls,
|
||||
Block::ExcerptHeader {
|
||||
buffer,
|
||||
range,
|
||||
starts_new_buffer,
|
||||
height,
|
||||
id,
|
||||
show_excerpt_controls,
|
||||
..
|
||||
} => {
|
||||
let include_root = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
|
||||
#[derive(Clone)]
|
||||
struct JumpData {
|
||||
position: Point,
|
||||
@@ -2117,227 +2125,233 @@ impl EditorElement {
|
||||
line_offset_from_top: u32,
|
||||
}
|
||||
|
||||
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
|
||||
let jump_path = ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
};
|
||||
let jump_anchor = range
|
||||
.primary
|
||||
.as_ref()
|
||||
.map_or(range.context.start, |primary| primary.start);
|
||||
|
||||
let excerpt_start = range.context.start;
|
||||
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
|
||||
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
|
||||
0
|
||||
} else {
|
||||
let excerpt_start_row =
|
||||
language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||
jump_position.row - excerpt_start_row
|
||||
};
|
||||
|
||||
let line_offset_from_top =
|
||||
block_row_start.0 + *height + offset_from_excerpt_start
|
||||
- snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32;
|
||||
|
||||
JumpData {
|
||||
position: jump_position,
|
||||
anchor: jump_anchor,
|
||||
path: jump_path,
|
||||
line_offset_from_top,
|
||||
}
|
||||
});
|
||||
|
||||
let icon_offset = gutter_dimensions.width
|
||||
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
|
||||
|
||||
let header_padding = px(6.0);
|
||||
|
||||
let mut result = v_flex().id(block_id).w_full();
|
||||
|
||||
if let Some(prev_excerpt) = prev_excerpt {
|
||||
if *show_excerpt_controls {
|
||||
result = result.child(
|
||||
h_flex()
|
||||
.w(icon_offset)
|
||||
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
.child(self.render_expand_excerpt_button(
|
||||
prev_excerpt.id,
|
||||
ExpandExcerptDirection::Down,
|
||||
IconName::ArrowDownFromLine,
|
||||
cx,
|
||||
)),
|
||||
);
|
||||
let element = if *starts_new_buffer {
|
||||
let path = buffer.resolve_file_path(cx, include_root);
|
||||
let mut filename = None;
|
||||
let mut parent_path = None;
|
||||
// Can't use .and_then() because `.file_name()` and `.parent()` return references :(
|
||||
if let Some(path) = path {
|
||||
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
|
||||
parent_path = path
|
||||
.parent()
|
||||
.map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(next_excerpt) = next_excerpt {
|
||||
let buffer = &next_excerpt.buffer;
|
||||
let range = &next_excerpt.range;
|
||||
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
|
||||
let jump_path = ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
};
|
||||
let jump_anchor = range
|
||||
.primary
|
||||
.as_ref()
|
||||
.map_or(range.context.start, |primary| primary.start);
|
||||
let header_padding = px(6.0);
|
||||
|
||||
let excerpt_start = range.context.start;
|
||||
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
|
||||
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
|
||||
0
|
||||
} else {
|
||||
let excerpt_start_row =
|
||||
language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||
jump_position.row - excerpt_start_row
|
||||
};
|
||||
|
||||
let line_offset_from_top =
|
||||
block_row_start.0 + *height + offset_from_excerpt_start
|
||||
- snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32;
|
||||
|
||||
JumpData {
|
||||
position: jump_position,
|
||||
anchor: jump_anchor,
|
||||
path: jump_path,
|
||||
line_offset_from_top,
|
||||
}
|
||||
});
|
||||
|
||||
if *starts_new_buffer {
|
||||
let include_root = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let path = buffer.resolve_file_path(cx, include_root);
|
||||
let filename = path
|
||||
.as_ref()
|
||||
.and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
|
||||
let parent_path = path.as_ref().and_then(|path| {
|
||||
Some(path.parent()?.to_string_lossy().to_string() + "/")
|
||||
});
|
||||
|
||||
result = result.child(
|
||||
div()
|
||||
.px(header_padding)
|
||||
.pt(header_padding)
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.child(
|
||||
h_flex()
|
||||
.id("path header block")
|
||||
.size_full()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(
|
||||
0.667,
|
||||
)))
|
||||
.px(gpui::px(12.))
|
||||
.rounded_md()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_subheader_background)
|
||||
.justify_between()
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||
.child(
|
||||
h_flex().gap_3().child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(div().child(path).text_color(
|
||||
cx.theme().colors().text_muted,
|
||||
))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.when_some(jump_data, |el, jump_data| {
|
||||
el.child(Icon::new(IconName::ArrowUpRight))
|
||||
.cursor_pointer()
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Jump to File",
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| {
|
||||
cx.stop_propagation()
|
||||
})
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
move |editor, _, cx| {
|
||||
editor.jump(
|
||||
jump_data.path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
}),
|
||||
),
|
||||
);
|
||||
if *show_excerpt_controls {
|
||||
result = result.child(
|
||||
h_flex()
|
||||
.w(icon_offset)
|
||||
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
.child(self.render_expand_excerpt_button(
|
||||
next_excerpt.id,
|
||||
ExpandExcerptDirection::Up,
|
||||
IconName::ArrowUpFromLine,
|
||||
cx,
|
||||
)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
result = result.child(
|
||||
v_flex()
|
||||
.id(("path excerpt header", EntityId::from(block_id)))
|
||||
.w_full()
|
||||
.px(header_padding)
|
||||
.pt(header_padding)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("excerpt header block")
|
||||
.group("excerpt-jump-action")
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||
.id("path header block")
|
||||
.h(2. * cx.line_height())
|
||||
.px(gpui::px(12.))
|
||||
.rounded_md()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_subheader_background)
|
||||
.justify_between()
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||
.child(
|
||||
h_flex().gap_3().child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(
|
||||
div()
|
||||
.child(path)
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.when_some(jump_data.clone(), |el, jump_data| {
|
||||
el.child(Icon::new(IconName::ArrowUpRight))
|
||||
.cursor_pointer()
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Jump to File", &OpenExcerpts, cx)
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| {
|
||||
cx.stop_propagation()
|
||||
})
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
move |editor, _, cx| {
|
||||
editor.jump(
|
||||
jump_data.path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.children(show_excerpt_controls.then(|| {
|
||||
h_flex()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.333)))
|
||||
.h(1. * cx.line_height())
|
||||
.pt_1()
|
||||
.justify_end()
|
||||
.flex_none()
|
||||
.w(icon_offset - header_padding)
|
||||
.child(
|
||||
ButtonLike::new("expand-icon")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(
|
||||
svg()
|
||||
.path(IconName::ArrowUpFromLine.path())
|
||||
.size(IconSize::XSmall.rems())
|
||||
.text_color(cx.theme().colors().editor_line_number)
|
||||
.group("")
|
||||
.hover(|style| {
|
||||
style.text_color(
|
||||
cx.theme()
|
||||
.colors()
|
||||
.editor_active_line_number,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
let id = *id;
|
||||
move |editor, _, cx| {
|
||||
editor.expand_excerpt(
|
||||
id,
|
||||
multi_buffer::ExpandExcerptDirection::Up,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Expand Excerpt",
|
||||
&ExpandExcerpts { lines: 0 },
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
v_flex()
|
||||
.id(("excerpt header", EntityId::from(block_id)))
|
||||
.w_full()
|
||||
.h(snapshot.excerpt_header_height() as f32 * cx.line_height())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.v_flex()
|
||||
.justify_start()
|
||||
.w_full()
|
||||
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.relative()
|
||||
.id("jump to collapsed context")
|
||||
.w(relative(1.0))
|
||||
.h_full()
|
||||
.child(
|
||||
div()
|
||||
.top(px(0.))
|
||||
.absolute()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().border_variant)
|
||||
.group_hover("excerpt-jump-action", |style| {
|
||||
style.bg(cx.theme().colors().border)
|
||||
}),
|
||||
)
|
||||
.cursor_pointer()
|
||||
.when_some(jump_data.clone(), |this, jump_data| {
|
||||
this.on_click(cx.listener_for(&self.editor, {
|
||||
let path = jump_data.path.clone();
|
||||
move |editor, _, cx| {
|
||||
cx.stop_propagation();
|
||||
|
||||
editor.jump(
|
||||
path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
format!(
|
||||
"Jump to {}:L{}",
|
||||
jump_data.path.path.display(),
|
||||
jump_data.position.row + 1
|
||||
),
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.flex_none()
|
||||
.w(icon_offset)
|
||||
.h_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w(icon_offset)
|
||||
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32
|
||||
* cx.line_height())
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
.child(if *show_excerpt_controls {
|
||||
self.render_expand_excerpt_button(
|
||||
next_excerpt.id,
|
||||
ExpandExcerptDirection::Up,
|
||||
IconName::ArrowUpFromLine,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
show_excerpt_controls
|
||||
.then(|| {
|
||||
ButtonLike::new("expand-icon")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(
|
||||
svg()
|
||||
.path(IconName::ArrowUpFromLine.path())
|
||||
.size(IconSize::XSmall.rems())
|
||||
.text_color(
|
||||
cx.theme().colors().editor_line_number,
|
||||
)
|
||||
.group("")
|
||||
.hover(|style| {
|
||||
style.text_color(
|
||||
cx.theme()
|
||||
.colors()
|
||||
.editor_active_line_number,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
let id = *id;
|
||||
move |editor, _, cx| {
|
||||
editor.expand_excerpt(
|
||||
id,
|
||||
multi_buffer::ExpandExcerptDirection::Up,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Expand Excerpt",
|
||||
&ExpandExcerpts { lines: 0 },
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
ButtonLike::new("jump-icon")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(
|
||||
@@ -2347,6 +2361,7 @@ impl EditorElement {
|
||||
.text_color(
|
||||
cx.theme().colors().border_variant,
|
||||
)
|
||||
.group("excerpt-jump-action")
|
||||
.group_hover(
|
||||
"excerpt-jump-action",
|
||||
|style| {
|
||||
@@ -2356,13 +2371,118 @@ impl EditorElement {
|
||||
},
|
||||
),
|
||||
)
|
||||
.when_some(jump_data.clone(), |this, jump_data| {
|
||||
this.on_click(cx.listener_for(&self.editor, {
|
||||
let path = jump_data.path.clone();
|
||||
move |editor, _, cx| {
|
||||
cx.stop_propagation();
|
||||
|
||||
editor.jump(
|
||||
path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
format!(
|
||||
"Jump to {}:L{}",
|
||||
jump_data.path.path.display(),
|
||||
jump_data.position.row + 1
|
||||
),
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
.group("excerpt-jump-action")
|
||||
.cursor_pointer()
|
||||
.when_some(jump_data.clone(), |this, jump_data| {
|
||||
this.on_click(cx.listener_for(&self.editor, {
|
||||
let path = jump_data.path.clone();
|
||||
move |editor, _, cx| {
|
||||
cx.stop_propagation();
|
||||
|
||||
result.into_any()
|
||||
editor.jump(
|
||||
path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
format!(
|
||||
"Jump to {}:L{}",
|
||||
jump_data.path.path.display(),
|
||||
jump_data.position.row + 1
|
||||
),
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
};
|
||||
element.into_any()
|
||||
}
|
||||
|
||||
Block::ExcerptFooter { id, .. } => {
|
||||
let element = v_flex()
|
||||
.id(("excerpt footer", EntityId::from(block_id)))
|
||||
.w_full()
|
||||
.h(snapshot.excerpt_footer_height() as f32 * cx.line_height())
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.flex_none()
|
||||
.w(gutter_dimensions.width
|
||||
- (gutter_dimensions.left_padding + gutter_dimensions.margin))
|
||||
.h_full()
|
||||
.child(
|
||||
ButtonLike::new("expand-icon")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(
|
||||
svg()
|
||||
.path(IconName::ArrowDownFromLine.path())
|
||||
.size(IconSize::XSmall.rems())
|
||||
.text_color(cx.theme().colors().editor_line_number)
|
||||
.group("")
|
||||
.hover(|style| {
|
||||
style.text_color(
|
||||
cx.theme().colors().editor_active_line_number,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
let id = *id;
|
||||
move |editor, _, cx| {
|
||||
editor.expand_excerpt(
|
||||
id,
|
||||
multi_buffer::ExpandExcerptDirection::Down,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Expand Excerpt",
|
||||
&ExpandExcerpts { lines: 0 },
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
element.into_any()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2389,33 +2509,6 @@ impl EditorElement {
|
||||
(element, final_size)
|
||||
}
|
||||
|
||||
fn render_expand_excerpt_button(
|
||||
&self,
|
||||
excerpt_id: ExcerptId,
|
||||
direction: ExpandExcerptDirection,
|
||||
icon: IconName,
|
||||
cx: &mut WindowContext,
|
||||
) -> ButtonLike {
|
||||
ButtonLike::new("expand-icon")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(
|
||||
svg()
|
||||
.path(icon.path())
|
||||
.size(IconSize::XSmall.rems())
|
||||
.text_color(cx.theme().colors().editor_line_number)
|
||||
.group("")
|
||||
.hover(|style| style.text_color(cx.theme().colors().editor_active_line_number)),
|
||||
)
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
move |editor, _, cx| {
|
||||
editor.expand_excerpt(excerpt_id, direction, cx);
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
move |cx| Tooltip::for_action("Expand Excerpt", &ExpandExcerpts { lines: 0 }, cx)
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_blocks(
|
||||
&self,
|
||||
@@ -3274,7 +3367,7 @@ impl EditorElement {
|
||||
let end_row_in_current_excerpt = snapshot
|
||||
.blocks_in_range(start_row..end_row)
|
||||
.find_map(|(start_row, block)| {
|
||||
if matches!(block, Block::ExcerptBoundary { .. }) {
|
||||
if matches!(block, Block::ExcerptHeader { .. }) {
|
||||
Some(start_row)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -952,7 +952,7 @@ mod tests {
|
||||
px(14.0),
|
||||
None,
|
||||
true,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
FoldPlaceholder::test(),
|
||||
|
||||
@@ -824,7 +824,7 @@ impl X11Client {
|
||||
Event::XkbStateNotify(event) => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.xkb.update_mask(
|
||||
dbg!(event).base_mods.into(),
|
||||
event.base_mods.into(),
|
||||
event.latched_mods.into(),
|
||||
event.locked_mods.into(),
|
||||
event.base_group as u32,
|
||||
@@ -836,7 +836,7 @@ impl X11Client {
|
||||
latched_layout: event.latched_group as u32,
|
||||
locked_layout: event.locked_group.into(),
|
||||
};
|
||||
let modifiers = dbg!(Modifiers::from_xkb(&state.xkb));
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
if state.modifiers == modifiers {
|
||||
drop(state);
|
||||
} else {
|
||||
@@ -919,7 +919,7 @@ impl X11Client {
|
||||
}));
|
||||
}
|
||||
Event::KeyRelease(event) => {
|
||||
let window = self.get_window(dbg!(event).event)?;
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
|
||||
@@ -3216,11 +3216,24 @@ impl<'a> WindowContext<'a> {
|
||||
self.draw();
|
||||
}
|
||||
|
||||
let node_id = self
|
||||
.window
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
let dispatch_path = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_path(self.focused_node_id());
|
||||
.dispatch_path(node_id);
|
||||
|
||||
let mut keystroke: Option<Keystroke> = None;
|
||||
|
||||
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
|
||||
if event.modifiers.number_of_modifiers() == 0
|
||||
@@ -3236,36 +3249,30 @@ impl<'a> WindowContext<'a> {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(key) = key {
|
||||
let keystroke = Keystroke {
|
||||
keystroke = Some(Keystroke {
|
||||
key: key.to_string(),
|
||||
ime_key: None,
|
||||
modifiers: Modifiers::default(),
|
||||
};
|
||||
self.finish_dispatch_keystroke(keystroke, &dispatch_path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
|
||||
&& event.modifiers.number_of_modifiers() == 1
|
||||
{
|
||||
self.window.pending_modifier.saw_keystroke = false;
|
||||
self.window.pending_modifier.saw_keystroke = false
|
||||
}
|
||||
self.window.pending_modifier.modifiers = event.modifiers;
|
||||
|
||||
self.finish_dispatch_key_event(event, &dispatch_path);
|
||||
self.window.pending_modifier.modifiers = event.modifiers
|
||||
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
self.window.pending_modifier.saw_keystroke = true;
|
||||
self.finish_dispatch_keystroke(key_down_event.keystroke.clone(), &dispatch_path);
|
||||
} else {
|
||||
self.finish_dispatch_key_event(event, &dispatch_path);
|
||||
keystroke = Some(key_down_event.keystroke.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_dispatch_keystroke(
|
||||
&self,
|
||||
keystroke: Keystroke,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
let Some(keystroke) = keystroke else {
|
||||
self.finish_dispatch_key_event(event, dispatch_path);
|
||||
return;
|
||||
};
|
||||
|
||||
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
|
||||
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
|
||||
currently_pending = PendingInput::default();
|
||||
@@ -3299,8 +3306,7 @@ impl<'a> WindowContext<'a> {
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
// FIXME: valid?
|
||||
.dispatch_path(cx.focused_node_id());
|
||||
.dispatch_path(node_id);
|
||||
|
||||
let to_replay = cx
|
||||
.window
|
||||
@@ -3328,13 +3334,13 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_dispatch_key_event(event, &dispatch_path)
|
||||
self.finish_dispatch_key_event(event, dispatch_path)
|
||||
}
|
||||
|
||||
fn finish_dispatch_key_event(
|
||||
&mut self,
|
||||
event: &dyn Any,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
self.dispatch_key_down_up_event(event, &dispatch_path);
|
||||
if !self.propagate_event {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "CSS"
|
||||
grammar = "css"
|
||||
path_suffixes = ["css", "postcss"]
|
||||
path_suffixes = ["css"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
|
||||
@@ -189,7 +189,6 @@ pub struct MultiBufferSnapshot {
|
||||
show_headers: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExcerptInfo {
|
||||
pub id: ExcerptId,
|
||||
pub buffer: BufferSnapshot,
|
||||
@@ -202,7 +201,6 @@ impl std::fmt::Debug for ExcerptInfo {
|
||||
f.debug_struct(type_name::<Self>())
|
||||
.field("id", &self.id)
|
||||
.field("buffer_id", &self.buffer_id)
|
||||
.field("path", &self.buffer.file().map(|f| f.path()))
|
||||
.field("range", &self.range)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@@ -6057,16 +6057,6 @@ impl LspStore {
|
||||
);
|
||||
})?;
|
||||
}
|
||||
"textDocument/rename" => {
|
||||
this.update(&mut cx, |this, _| {
|
||||
if let Some(server) = this.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.rename_provider = None
|
||||
})
|
||||
}
|
||||
})?;
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
this.update(&mut cx, |this, _| {
|
||||
if let Some(server) = this.language_server_for_id(server_id)
|
||||
|
||||
@@ -2701,6 +2701,7 @@ impl ProjectPanel {
|
||||
.cursor_default()
|
||||
.when(self.width.is_some(), |this| {
|
||||
this.children(Scrollbar::horizontal(
|
||||
//percentage as f32..end_offset as f32,
|
||||
self.horizontal_scrollbar_state.clone(),
|
||||
))
|
||||
}),
|
||||
@@ -2917,9 +2918,7 @@ impl Render for ProjectPanel {
|
||||
.track_scroll(self.scroll_handle.clone()),
|
||||
)
|
||||
.children(self.render_vertical_scrollbar(cx))
|
||||
.when_some(self.render_horizontal_scrollbar(cx), |this, scrollbar| {
|
||||
this.pb_4().child(scrollbar)
|
||||
})
|
||||
.children(self.render_horizontal_scrollbar(cx))
|
||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||
deferred(
|
||||
anchored()
|
||||
|
||||
@@ -175,7 +175,7 @@ impl Render for SshPrompt {
|
||||
.child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.child(if self.error_message.is_some() {
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Medium)
|
||||
@@ -195,7 +195,6 @@ impl Render for SshPrompt {
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.ml_1()
|
||||
.text_ellipsis()
|
||||
.overflow_x_hidden()
|
||||
.when_some(self.error_message.as_ref(), |el, error| {
|
||||
@@ -206,7 +205,7 @@ impl Render for SshPrompt {
|
||||
|el| {
|
||||
el.child(
|
||||
Label::new(format!(
|
||||
"{}…",
|
||||
"-{}…",
|
||||
self.status_message.clone().unwrap()
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use prost::Message as _;
|
||||
use rpc::proto::Envelope;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct MessageId(pub u32);
|
||||
@@ -13,14 +14,25 @@ pub fn message_len_from_buffer(buffer: &[u8]) -> MessageLen {
|
||||
MessageLen::from_le_bytes(buffer.try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn write_to_global_log(message: &str) {
|
||||
use std::io::Write;
|
||||
let mut file =
|
||||
std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("/Users/thorstenball/mylog.log".to_string())
|
||||
.expect("Failed to open log file");
|
||||
writeln!(file, "{}", message).expect("Failed to write to log file");
|
||||
}
|
||||
|
||||
pub async fn read_message_with_len<S: AsyncRead + Unpin>(
|
||||
stream: &mut S,
|
||||
buffer: &mut Vec<u8>,
|
||||
message_len: MessageLen,
|
||||
) -> Result<Envelope> {
|
||||
buffer.resize(message_len as usize, 0);
|
||||
stream.read_exact(buffer).await?;
|
||||
Ok(Envelope::decode(buffer.as_slice())?)
|
||||
stream.read_exact(buffer).await.context("read exact failed")?;
|
||||
Ok(Envelope::decode(buffer.as_slice()).context("decode failed")?)
|
||||
}
|
||||
|
||||
pub async fn read_message<S: AsyncRead + Unpin>(
|
||||
@@ -28,11 +40,13 @@ pub async fn read_message<S: AsyncRead + Unpin>(
|
||||
buffer: &mut Vec<u8>,
|
||||
) -> Result<Envelope> {
|
||||
buffer.resize(MESSAGE_LEN_SIZE, 0);
|
||||
stream.read_exact(buffer).await?;
|
||||
|
||||
stream.read_exact(buffer).await.context("read exact failed")?;
|
||||
// log::debug!("read_exact DONE");
|
||||
let len = message_len_from_buffer(buffer);
|
||||
let result = read_message_with_len(stream, buffer, len).await;
|
||||
result
|
||||
// log::debug!("received message_len_from_buffer: {}", len);
|
||||
let s = read_message_with_len(stream, buffer, len).await.with_context(|| format!("read message with len={} failed", len))?;
|
||||
// log::debug!("received message with len: {}", len);
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub async fn write_message<S: AsyncWrite + Unpin>(
|
||||
@@ -51,6 +65,23 @@ pub async fn write_message<S: AsyncWrite + Unpin>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_message_log<S: AsyncWrite + Unpin>(
|
||||
stream: &mut S,
|
||||
buffer: &mut Vec<u8>,
|
||||
message: Envelope,
|
||||
) -> Result<()> {
|
||||
let message_len = message.encoded_len() as u32;
|
||||
log::debug!("write_message_log. message_len: {}", message_len);
|
||||
stream
|
||||
.write_all(message_len.to_le_bytes().as_slice())
|
||||
.await.context("failed to write message_len")?;
|
||||
buffer.clear();
|
||||
buffer.reserve(message_len as usize);
|
||||
message.encode(buffer).context("Failed to encode message")?;
|
||||
stream.write_all(buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn read_message_raw<S: AsyncRead + Unpin>(
|
||||
stream: &mut S,
|
||||
buffer: &mut Vec<u8>,
|
||||
@@ -59,6 +90,7 @@ pub async fn read_message_raw<S: AsyncRead + Unpin>(
|
||||
stream.read_exact(buffer).await?;
|
||||
|
||||
let message_len = message_len_from_buffer(buffer);
|
||||
log::debug!("read_message_raw. message_len: {}", message_len);
|
||||
buffer.resize(message_len as usize, 0);
|
||||
stream.read_exact(buffer).await?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
json_log::LogRecord,
|
||||
protocol::{
|
||||
message_len_from_buffer, read_message_with_len, write_message, MessageId, MESSAGE_LEN_SIZE,
|
||||
message_len_from_buffer, read_message_with_len, write_message_log, MessageId, MESSAGE_LEN_SIZE,
|
||||
},
|
||||
proxy::ProxyLaunchError,
|
||||
};
|
||||
@@ -11,9 +11,7 @@ use futures::{
|
||||
channel::{
|
||||
mpsc::{self, Sender, UnboundedReceiver, UnboundedSender},
|
||||
oneshot,
|
||||
},
|
||||
future::BoxFuture,
|
||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, SinkExt, StreamExt as _,
|
||||
}, future::BoxFuture, select_biased, AsyncReadExt as _, AsyncWriteExt as _, Future, FutureExt as _, SinkExt, StreamExt as _
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SemanticVersion, Task,
|
||||
@@ -295,13 +293,17 @@ impl ChannelForwarder {
|
||||
loop {
|
||||
select_biased! {
|
||||
_ = quit_rx.next().fuse() => {
|
||||
log::debug!("ChannelForwarder. quit");
|
||||
break;
|
||||
},
|
||||
incoming_envelope = proxy_incoming_rx.next().fuse() => {
|
||||
// log::debug!("ChannelForwarder. incoming envelope.");
|
||||
if let Some(envelope) = incoming_envelope {
|
||||
// let id = envelope.id;
|
||||
if incoming_tx.send(envelope).await.is_err() {
|
||||
break;
|
||||
}
|
||||
// log::debug!("ChannelForwarder. incoming envelope sent. id: {:?}", id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -309,8 +311,10 @@ impl ChannelForwarder {
|
||||
outgoing_envelope = outgoing_rx.next().fuse() => {
|
||||
if let Some(envelope) = outgoing_envelope {
|
||||
if proxy_outgoing_tx.send(envelope).await.is_err() {
|
||||
// log::debug!("ChannelForwarder. outgoing envelope errored.");
|
||||
break;
|
||||
}
|
||||
// log::debug!("ChannelForwarder. outgoing envelope sent.");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -621,6 +625,7 @@ impl SshRemoteClient {
|
||||
}
|
||||
|
||||
fn reconnect(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
log::debug!("reconnect...");
|
||||
let mut lock = self.state.lock();
|
||||
|
||||
let can_reconnect = lock
|
||||
@@ -637,6 +642,7 @@ impl SshRemoteClient {
|
||||
return Err(anyhow!(error));
|
||||
}
|
||||
|
||||
log::debug!("reconnect... taking state.");
|
||||
let state = lock.take().unwrap();
|
||||
let (attempts, mut ssh_connection, delegate, forwarder) = match state {
|
||||
State::Connected {
|
||||
@@ -835,6 +841,7 @@ impl SshRemoteClient {
|
||||
keepalive_timer.set(cx.background_executor().timer(HEARTBEAT_INTERVAL).fuse());
|
||||
|
||||
if missed_heartbeats != 0 {
|
||||
log::debug!("Resetting missed heartbeats to 0");
|
||||
missed_heartbeats = 0;
|
||||
this.update(&mut cx, |this, mut cx| {
|
||||
this.handle_heartbeat_result(missed_heartbeats, &mut cx)
|
||||
@@ -862,6 +869,7 @@ impl SshRemoteClient {
|
||||
MAX_MISSED_HEARTBEATS
|
||||
);
|
||||
} else if missed_heartbeats != 0 {
|
||||
log::debug!("Resetting missed heartbeats to 0");
|
||||
missed_heartbeats = 0;
|
||||
} else {
|
||||
continue;
|
||||
@@ -921,90 +929,102 @@ impl SshRemoteClient {
|
||||
let mut child_stdout = ssh_proxy_process.stdout.take().unwrap();
|
||||
let mut child_stdin = ssh_proxy_process.stdin.take().unwrap();
|
||||
|
||||
let mut stdin_buffer = Vec::new();
|
||||
let mut stdout_buffer = Vec::new();
|
||||
let mut stderr_buffer = Vec::new();
|
||||
let mut stderr_offset = 0;
|
||||
let io_task = cx.background_executor().spawn(async move {
|
||||
let mut stdin_buffer = Vec::new();
|
||||
let mut stdout_buffer = Vec::new();
|
||||
let mut stderr_buffer = Vec::new();
|
||||
let mut stderr_offset = 0;
|
||||
|
||||
let stdin_task = cx.background_executor().spawn(async move {
|
||||
while let Some(outgoing) = outgoing_rx.next().await {
|
||||
write_message(&mut child_stdin, &mut stdin_buffer, outgoing).await?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
});
|
||||
|
||||
let stdout_task = cx.background_executor().spawn({
|
||||
let mut connection_activity_tx = connection_activity_tx.clone();
|
||||
async move {
|
||||
loop {
|
||||
stdout_buffer.resize(MESSAGE_LEN_SIZE, 0);
|
||||
let len = child_stdout.read(&mut stdout_buffer).await?;
|
||||
|
||||
if len == 0 {
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
if len < MESSAGE_LEN_SIZE {
|
||||
child_stdout.read_exact(&mut stdout_buffer[len..]).await?;
|
||||
}
|
||||
|
||||
let message_len = message_len_from_buffer(&stdout_buffer);
|
||||
let envelope =
|
||||
read_message_with_len(&mut child_stdout, &mut stdout_buffer, message_len)
|
||||
.await?;
|
||||
connection_activity_tx.try_send(()).ok();
|
||||
incoming_tx.unbounded_send(envelope).ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let stderr_task: Task<anyhow::Result<()>> = cx.background_executor().spawn(async move {
|
||||
loop {
|
||||
stdout_buffer.resize(MESSAGE_LEN_SIZE, 0);
|
||||
stderr_buffer.resize(stderr_offset + 1024, 0);
|
||||
|
||||
let len = child_stderr
|
||||
.read(&mut stderr_buffer[stderr_offset..])
|
||||
.await?;
|
||||
select_biased! {
|
||||
outgoing = outgoing_rx.next().fuse() => {
|
||||
let Some(outgoing) = outgoing else {
|
||||
return anyhow::Ok(None);
|
||||
};
|
||||
|
||||
stderr_offset += len;
|
||||
let mut start_ix = 0;
|
||||
while let Some(ix) = stderr_buffer[start_ix..stderr_offset]
|
||||
.iter()
|
||||
.position(|b| b == &b'\n')
|
||||
{
|
||||
let line_ix = start_ix + ix;
|
||||
let content = &stderr_buffer[start_ix..line_ix];
|
||||
start_ix = line_ix + 1;
|
||||
if let Ok(record) = serde_json::from_slice::<LogRecord>(content) {
|
||||
record.log(log::logger())
|
||||
} else {
|
||||
eprintln!("(remote) {}", String::from_utf8_lossy(content));
|
||||
// {"level":2,"module_path":"remote_server::unix","file":"crates/remote_server/src/unix.rs","line":281,"message":"(remote server) error reading message on stdin: read message with len=3793265416 failed."}
|
||||
write_message_log(&mut child_stdin, &mut stdin_buffer, outgoing).await?;
|
||||
child_stdin.flush().await?;
|
||||
}
|
||||
|
||||
result = child_stdout.read(&mut stdout_buffer).fuse() => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
log::debug!("child_stdout. 0. closing channels");
|
||||
child_stdin.close().await?;
|
||||
outgoing_rx.close();
|
||||
// log::debug!("child_stdout. 0. getting status");
|
||||
let status = ssh_proxy_process.status().await?;
|
||||
// log::debug!("child_stdout. 0. got status");
|
||||
// If we don't have a code, we assume process
|
||||
// has been killed and treat it as non-zero exit
|
||||
// code
|
||||
log::debug!("child_stdout. 0. returning");
|
||||
return Ok(status.code().or_else(|| Some(1)));
|
||||
}
|
||||
Ok(len) => {
|
||||
if len < stdout_buffer.len() {
|
||||
child_stdout.read_exact(&mut stdout_buffer[len..]).await?;
|
||||
}
|
||||
|
||||
let message_len = message_len_from_buffer(&stdout_buffer);
|
||||
match read_message_with_len(&mut child_stdout, &mut stdout_buffer, message_len).await {
|
||||
Ok(envelope) => {
|
||||
connection_activity_tx.try_send(()).ok();
|
||||
incoming_tx.unbounded_send(envelope).ok();
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("error decoding message {error:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
Err(anyhow!("error reading stdout: {error:?}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = child_stderr.read(&mut stderr_buffer[stderr_offset..]).fuse() => {
|
||||
match result {
|
||||
Ok(len) => {
|
||||
// log::debug!("child_stderr. len: {}", len);
|
||||
stderr_offset += len;
|
||||
let mut start_ix = 0;
|
||||
while let Some(ix) = stderr_buffer[start_ix..stderr_offset].iter().position(|b| b == &b'\n') {
|
||||
let line_ix = start_ix + ix;
|
||||
let content = &stderr_buffer[start_ix..line_ix];
|
||||
start_ix = line_ix + 1;
|
||||
if let Ok(record) = serde_json::from_slice::<LogRecord>(content) {
|
||||
record.log(log::logger())
|
||||
} else {
|
||||
eprintln!("(remote) {}", String::from_utf8_lossy(content));
|
||||
}
|
||||
}
|
||||
stderr_buffer.drain(0..start_ix);
|
||||
stderr_offset -= start_ix;
|
||||
|
||||
connection_activity_tx.try_send(()).ok();
|
||||
}
|
||||
Err(error) => {
|
||||
Err(anyhow!("error reading stderr: {error:?}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stderr_buffer.drain(0..start_ix);
|
||||
stderr_offset -= start_ix;
|
||||
|
||||
connection_activity_tx.try_send(()).ok();
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let result = futures::select! {
|
||||
result = stdin_task.fuse() => {
|
||||
result.context("stdin")
|
||||
}
|
||||
result = stdout_task.fuse() => {
|
||||
result.context("stdout")
|
||||
}
|
||||
result = stderr_task.fuse() => {
|
||||
result.context("stderr")
|
||||
}
|
||||
};
|
||||
let result = io_task.await;
|
||||
|
||||
log::debug!("IO Task finished. result: {:?}", result);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let exit_code = ssh_proxy_process.status().await?.code().unwrap_or(1);
|
||||
|
||||
Ok(Some(exit_code)) => {
|
||||
log::debug!("Proxy exited with code {:?}", exit_code);
|
||||
if let Some(error) = ProxyLaunchError::from_exit_code(exit_code) {
|
||||
match error {
|
||||
ProxyLaunchError::ServerNotRunning => {
|
||||
@@ -1022,6 +1042,7 @@ impl SshRemoteClient {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
log::warn!("ssh io task died with error: {:?}. reconnecting...", error);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
@@ -1029,7 +1050,6 @@ impl SshRemoteClient {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -1043,6 +1063,7 @@ impl SshRemoteClient {
|
||||
cx: &mut ModelContext<Self>,
|
||||
map: impl FnOnce(&State) -> Option<State>,
|
||||
) {
|
||||
log::debug!("try_set_state ...");
|
||||
let mut lock = self.state.lock();
|
||||
let new_state = lock.as_ref().and_then(map);
|
||||
|
||||
@@ -1050,6 +1071,7 @@ impl SshRemoteClient {
|
||||
lock.replace(new_state);
|
||||
cx.notify();
|
||||
}
|
||||
log::debug!("try_set_state done");
|
||||
}
|
||||
|
||||
fn set_state(&self, state: State, cx: &mut ModelContext<Self>) {
|
||||
@@ -1203,7 +1225,6 @@ impl SshRemoteConnection {
|
||||
delegate: Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
use futures::AsyncWriteExt as _;
|
||||
use futures::{io::BufReader, AsyncBufReadExt as _};
|
||||
use smol::{fs::unix::PermissionsExt as _, net::unix::UnixListener};
|
||||
use util::ResultExt as _;
|
||||
@@ -1273,8 +1294,8 @@ impl SshRemoteConnection {
|
||||
.args(connection_options.additional_args().unwrap_or(&Vec::new()))
|
||||
.args([
|
||||
"-N",
|
||||
"-o",
|
||||
"ControlPersist=no",
|
||||
// "-o",
|
||||
// "ControlPersist=no",
|
||||
"-o",
|
||||
"ControlMaster=yes",
|
||||
"-o",
|
||||
@@ -1319,10 +1340,7 @@ impl SshRemoteConnection {
|
||||
let mut stderr = master_process.stderr.take().unwrap();
|
||||
stderr.read_to_end(&mut output).await?;
|
||||
|
||||
let error_message = format!(
|
||||
"failed to connect: {}",
|
||||
String::from_utf8_lossy(&output).trim()
|
||||
);
|
||||
let error_message = format!("failed to connect: {}", String::from_utf8_lossy(&output));
|
||||
delegate.set_error(error_message.clone(), cx);
|
||||
Err(anyhow!(error_message))?;
|
||||
}
|
||||
@@ -1383,14 +1401,14 @@ impl SshRemoteConnection {
|
||||
let server_mode = 0o755;
|
||||
|
||||
let t0 = Instant::now();
|
||||
delegate.set_status(Some("Uploading remote development server"), cx);
|
||||
delegate.set_status(Some("uploading remote development server"), cx);
|
||||
log::info!("uploading remote development server ({}kb)", size / 1024);
|
||||
self.upload_file(&src_path, &dst_path_gz)
|
||||
.await
|
||||
.context("failed to upload server binary")?;
|
||||
log::info!("uploaded remote development server in {:?}", t0.elapsed());
|
||||
|
||||
delegate.set_status(Some("Extracting remote development server"), cx);
|
||||
delegate.set_status(Some("extracting remote development server"), cx);
|
||||
run_cmd(
|
||||
self.socket
|
||||
.ssh_command("gunzip")
|
||||
@@ -1399,7 +1417,7 @@ impl SshRemoteConnection {
|
||||
)
|
||||
.await?;
|
||||
|
||||
delegate.set_status(Some("Marking remote development server executable"), cx);
|
||||
delegate.set_status(Some("unzipping remote development server"), cx);
|
||||
run_cmd(
|
||||
self.socket
|
||||
.ssh_command("chmod")
|
||||
@@ -1520,20 +1538,21 @@ impl ChannelClient {
|
||||
build_typed_envelope(peer_id, Instant::now(), incoming)
|
||||
{
|
||||
let type_name = envelope.payload_type_name();
|
||||
let id = envelope.message_id();
|
||||
if let Some(future) = ProtoMessageHandlerSet::handle_message(
|
||||
&this.message_handlers,
|
||||
envelope,
|
||||
this.clone().into(),
|
||||
cx.clone(),
|
||||
) {
|
||||
log::debug!("ssh message received. name:{type_name}");
|
||||
log::debug!("ssh message received. name:{type_name} id:{id}");
|
||||
match future.await {
|
||||
Ok(_) => {
|
||||
log::debug!("ssh message handled. name:{type_name}");
|
||||
log::debug!("ssh message handled. name:{type_name} id:{id}");
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
"error handling message. type:{type_name}, error:{error}",
|
||||
"error handling message. type:{type_name}, error:{error} id:{id}",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1574,7 +1593,15 @@ impl ChannelClient {
|
||||
log::debug!("ssh request start. name:{}", T::NAME);
|
||||
let response = self.request_dynamic(payload.into_envelope(0, None, None), T::NAME);
|
||||
async move {
|
||||
let response = response.await?;
|
||||
let response = futures::select! {
|
||||
response = response.fuse() => response?,
|
||||
_ = async {
|
||||
loop {
|
||||
smol::Timer::after(Duration::from_secs(3)).await;
|
||||
log::debug!("Still waiting for response. name:{}", T::NAME);
|
||||
}
|
||||
}.fuse() => unreachable!(),
|
||||
};
|
||||
log::debug!("ssh request finish. name:{}", T::NAME);
|
||||
T::Response::from_envelope(response)
|
||||
.ok_or_else(|| anyhow!("received a response of the wrong type"))
|
||||
@@ -1597,15 +1624,16 @@ impl ChannelClient {
|
||||
|
||||
pub fn send<T: EnvelopedMessage>(&self, payload: T) -> Result<()> {
|
||||
log::debug!("ssh send name:{}", T::NAME);
|
||||
self.send_dynamic(payload.into_envelope(0, None, None))
|
||||
self.send_dynamic(payload.into_envelope(0, None, None), T::NAME)
|
||||
}
|
||||
|
||||
pub fn request_dynamic(
|
||||
fn request_dynamic(
|
||||
&self,
|
||||
mut envelope: proto::Envelope,
|
||||
type_name: &'static str,
|
||||
) -> impl 'static + Future<Output = Result<proto::Envelope>> {
|
||||
envelope.id = self.next_message_id.fetch_add(1, SeqCst);
|
||||
log::debug!("ssh send_dynamic name:{} id:{}", type_name, envelope.id);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let mut response_channels_lock = self.response_channels.lock();
|
||||
response_channels_lock.insert(MessageId(envelope.id), tx);
|
||||
@@ -1625,8 +1653,9 @@ impl ChannelClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_dynamic(&self, mut envelope: proto::Envelope) -> Result<()> {
|
||||
fn send_dynamic(&self, mut envelope: proto::Envelope, message_type: &'static str) -> Result<()> {
|
||||
envelope.id = self.next_message_id.fetch_add(1, SeqCst);
|
||||
log::debug!("ssh send name:{} id:{}", message_type, envelope.id);
|
||||
self.outgoing_tx.unbounded_send(envelope)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1641,12 +1670,12 @@ impl ProtoClient for ChannelClient {
|
||||
self.request_dynamic(envelope, request_type).boxed()
|
||||
}
|
||||
|
||||
fn send(&self, envelope: proto::Envelope, _message_type: &'static str) -> Result<()> {
|
||||
self.send_dynamic(envelope)
|
||||
fn send(&self, envelope: proto::Envelope, message_type: &'static str) -> Result<()> {
|
||||
self.send_dynamic(envelope, message_type)
|
||||
}
|
||||
|
||||
fn send_response(&self, envelope: Envelope, _message_type: &'static str) -> anyhow::Result<()> {
|
||||
self.send_dynamic(envelope)
|
||||
fn send_response(&self, envelope: Envelope, message_type: &'static str) -> anyhow::Result<()> {
|
||||
self.send_dynamic(envelope, message_type)
|
||||
}
|
||||
|
||||
fn message_handler_set(&self) -> &Mutex<ProtoMessageHandlerSet> {
|
||||
|
||||
@@ -31,7 +31,7 @@ env_logger.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
git_hosting_providers.workspace = true
|
||||
# git_hosting_providers.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
|
||||
@@ -5,14 +5,13 @@ use client::ProxySettings;
|
||||
use fs::{Fs, RealFs};
|
||||
use futures::channel::mpsc;
|
||||
use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
// use git::GitHostingProviderRegistry;
|
||||
use gpui::{AppContext, Context as _, ModelContext, UpdateGlobal as _};
|
||||
use http_client::{read_proxy_from_env, Uri};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::{NodeBinaryOptions, NodeRuntime};
|
||||
use paths::logs_dir;
|
||||
use project::project_settings::ProjectSettings;
|
||||
|
||||
use remote::proxy::ProxyLaunchError;
|
||||
use remote::ssh_session::ChannelClient;
|
||||
use remote::{
|
||||
@@ -35,8 +34,61 @@ use std::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
fn init_logging_proxy() {
|
||||
env_logger::builder()
|
||||
|
||||
// fn init_logging_proxy() {
|
||||
// env_logger::builder()
|
||||
// .format(|buf, record| {
|
||||
// let mut log_record = LogRecord::new(record);
|
||||
// log_record.message = format!("(remote proxy) {}", log_record.message);
|
||||
// serde_json::to_writer(&mut *buf, &log_record)?;
|
||||
// buf.write_all(b"\n")?;
|
||||
// Ok(())
|
||||
// })
|
||||
// .init();
|
||||
// }
|
||||
|
||||
fn init_logging_proxy(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
|
||||
struct MultiWrite {
|
||||
file: Box<dyn std::io::Write + Send + 'static>,
|
||||
channel: Sender<Vec<u8>>,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::io::Write for MultiWrite {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let written = self.file.write(buf)?;
|
||||
self.buffer.extend_from_slice(&buf[..written]);
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.channel
|
||||
.send_blocking(self.buffer.clone())
|
||||
.map_err(|error| std::io::Error::new(std::io::ErrorKind::Other, error))?;
|
||||
self.buffer.clear();
|
||||
self.file.flush()
|
||||
}
|
||||
}
|
||||
|
||||
let log_file = Box::new(if log_file_path.exists() {
|
||||
std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.context("Failed to open log file in append mode")?
|
||||
} else {
|
||||
std::fs::File::create(&log_file_path).context("Failed to create log file")?
|
||||
});
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
|
||||
let target = Box::new(MultiWrite {
|
||||
file: log_file,
|
||||
channel: tx,
|
||||
buffer: Vec::new(),
|
||||
});
|
||||
|
||||
env_logger::Builder::from_default_env()
|
||||
.target(env_logger::Target::Pipe(target))
|
||||
.format(|buf, record| {
|
||||
let mut log_record = LogRecord::new(record);
|
||||
log_record.message = format!("(remote proxy) {}", log_record.message);
|
||||
@@ -45,6 +97,8 @@ fn init_logging_proxy() {
|
||||
Ok(())
|
||||
})
|
||||
.init();
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
|
||||
@@ -214,32 +268,28 @@ fn start_server(
|
||||
|
||||
let mut input_buffer = Vec::new();
|
||||
let mut output_buffer = Vec::new();
|
||||
|
||||
let (mut stdin_msg_tx, mut stdin_msg_rx) = mpsc::unbounded::<Envelope>();
|
||||
cx.background_executor().spawn(async move {
|
||||
while let Ok(msg) = read_message(&mut stdin_stream, &mut input_buffer).await {
|
||||
if let Err(_) = stdin_msg_tx.send(msg).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
|
||||
loop {
|
||||
|
||||
select_biased! {
|
||||
_ = app_quit_rx.next().fuse() => {
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
stdin_message = stdin_msg_rx.next().fuse() => {
|
||||
let Some(message) = stdin_message else {
|
||||
log::warn!("error reading message on stdin. exiting.");
|
||||
break;
|
||||
stdin_message = read_message(&mut stdin_stream, &mut input_buffer).fuse() => {
|
||||
let message = match stdin_message {
|
||||
Ok(message) => message,
|
||||
Err(error) => {
|
||||
log::warn!("error reading message on stdin: {}.", error);
|
||||
// std::process::exit(1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
log::debug!("got message on stdin. forwarding to application.");
|
||||
if let Err(error) = incoming_tx.unbounded_send(message) {
|
||||
log::error!("failed to send message to application: {:?}. exiting.", error);
|
||||
// std::process::exit(1);
|
||||
return Err(anyhow!(error));
|
||||
}
|
||||
// log::debug!("forwarded message to application.");
|
||||
}
|
||||
|
||||
outgoing_message = outgoing_rx.next().fuse() => {
|
||||
@@ -262,14 +312,18 @@ fn start_server(
|
||||
|
||||
log_message = log_rx.next().fuse() => {
|
||||
if let Some(log_message) = log_message {
|
||||
// write_to_global_log("writing log message...");
|
||||
if let Err(error) = stderr_stream.write_all(&log_message).await {
|
||||
log::error!("failed to write log message to stderr: {:?}", error);
|
||||
// write_to_global_log(format!("writing log message to stderr failed: {:?}", error).as_str());
|
||||
// log::error!("failed to write log message to stderr: {:?}", error);
|
||||
break;
|
||||
}
|
||||
if let Err(error) = stderr_stream.flush().await {
|
||||
log::error!("failed to flush stderr stream: {:?}", error);
|
||||
// write_to_global_log(&format!("failed to flush stderr stream: {:?}", error));
|
||||
// log::error!("failed to flush stderr stream: {:?}", error);
|
||||
break;
|
||||
}
|
||||
// write_to_global_log("writing log message... DONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,7 +378,7 @@ pub fn execute_run(
|
||||
|
||||
log::info!("starting headless gpui app");
|
||||
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
// let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
gpui::App::headless().run(move |cx| {
|
||||
settings::init(cx);
|
||||
HeadlessProject::init(cx);
|
||||
@@ -334,8 +388,8 @@ pub fn execute_run(
|
||||
|
||||
client::init_settings(cx);
|
||||
|
||||
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
git_hosting_providers::init(cx);
|
||||
// GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
// git_hosting_providers::init(cx);
|
||||
|
||||
let project = cx.new_model(|cx| {
|
||||
let fs = Arc::new(RealFs::new(Default::default(), None));
|
||||
@@ -412,7 +466,8 @@ impl ServerPaths {
|
||||
}
|
||||
|
||||
pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
|
||||
init_logging_proxy();
|
||||
let log_file = paths::remote_server_state_dir().join(&identifier).join("proxy.log");
|
||||
init_logging_proxy(log_file).log_err();
|
||||
init_panic_hook();
|
||||
|
||||
log::info!("starting proxy process. PID: {}", std::process::id());
|
||||
@@ -452,6 +507,7 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
|
||||
let mut stream = smol::net::unix::UnixStream::connect(&server_paths.stderr_socket).await?;
|
||||
let mut stderr_buffer = vec![0; 2048];
|
||||
loop {
|
||||
log::debug!("proxy reading message from stderr socket");
|
||||
match stream.read(&mut stderr_buffer).await {
|
||||
Ok(0) => {
|
||||
let error =
|
||||
@@ -607,6 +663,7 @@ where
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
loop {
|
||||
log::debug!("proxy reading message from {}", socket_name);
|
||||
read_message_raw(&mut reader, &mut buffer)
|
||||
.await
|
||||
.with_context(|| format!("failed to read message from {}", socket_name))?;
|
||||
@@ -626,6 +683,7 @@ async fn write_size_prefixed_buffer<S: AsyncWrite + Unpin>(
|
||||
buffer: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let len = buffer.len() as u32;
|
||||
log::debug!("write_size_prefixed_buffer. writing len: {}", len);
|
||||
stream.write_all(len.to_le_bytes().as_slice()).await?;
|
||||
stream.write_all(buffer).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -17,7 +17,8 @@ use editor::{
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _};
|
||||
use gpui::{
|
||||
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView,
|
||||
div, prelude::*, EntityId, EventEmitter, Model, Render, Subscription, Task, View, ViewContext,
|
||||
WeakView,
|
||||
};
|
||||
use language::Point;
|
||||
use project::Fs;
|
||||
@@ -148,21 +149,23 @@ impl EditorBlock {
|
||||
.w(text_line_height)
|
||||
.h(text_line_height)
|
||||
.child(
|
||||
IconButton::new("close_output_area", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Close output area", cx))
|
||||
.on_click(move |_, cx| {
|
||||
if let BlockId::Custom(block_id) = block_id {
|
||||
(on_close)(block_id, cx)
|
||||
}
|
||||
}),
|
||||
IconButton::new(
|
||||
("close_output_area", EntityId::from(cx.block_id)),
|
||||
IconName::Close,
|
||||
)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Close output area", cx))
|
||||
.on_click(move |_, cx| {
|
||||
if let BlockId::Custom(block_id) = block_id {
|
||||
(on_close)(block_id, cx)
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
div()
|
||||
.id(cx.block_id)
|
||||
.flex()
|
||||
.items_start()
|
||||
.min_h(text_line_height)
|
||||
|
||||
@@ -44,12 +44,8 @@ impl ReqwestClient {
|
||||
let mut client = reqwest::Client::builder()
|
||||
.use_rustls_tls()
|
||||
.default_headers(map);
|
||||
if let Some(proxy) = proxy.clone().and_then(|proxy_uri| {
|
||||
reqwest::Proxy::all(proxy_uri.to_string())
|
||||
.inspect_err(|e| log::error!("Failed to parse proxy URI {}: {}", proxy_uri, e))
|
||||
.ok()
|
||||
}) {
|
||||
client = client.proxy(proxy);
|
||||
if let Some(proxy) = proxy.clone() {
|
||||
client = client.proxy(reqwest::Proxy::all(proxy.to_string())?);
|
||||
}
|
||||
let client = client.build()?;
|
||||
let mut client: ReqwestClient = client.into();
|
||||
@@ -236,47 +232,3 @@ impl http_client::HttpClient for ReqwestClient {
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use http_client::{http, HttpClient};
|
||||
|
||||
use crate::ReqwestClient;
|
||||
|
||||
#[test]
|
||||
fn test_proxy_uri() {
|
||||
let client = ReqwestClient::new();
|
||||
assert_eq!(client.proxy(), None);
|
||||
|
||||
let proxy = http::Uri::from_static("http://localhost:10809");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
|
||||
let proxy = http::Uri::from_static("https://localhost:10809");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
|
||||
let proxy = http::Uri::from_static("socks4://localhost:10808");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
|
||||
let proxy = http::Uri::from_static("socks4a://localhost:10808");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
|
||||
let proxy = http::Uri::from_static("socks5://localhost:10808");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
|
||||
let proxy = http::Uri::from_static("socks5h://localhost:10808");
|
||||
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
|
||||
assert_eq!(client.proxy(), Some(&proxy));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_proxy_uri() {
|
||||
let proxy = http::Uri::from_static("file:///etc/hosts");
|
||||
ReqwestClient::proxy_and_user_agent(Some(proxy), "test").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ impl PickerDelegate for TabSwitcherDelegate {
|
||||
icon.color(git_status_color.unwrap_or_default())
|
||||
});
|
||||
|
||||
let indicator = render_item_indicator(tab_match.item.boxed_clone(), None, cx);
|
||||
let indicator = render_item_indicator(tab_match.item.boxed_clone(), cx);
|
||||
let indicator_color = if let Some(ref indicator) = indicator {
|
||||
indicator.color
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ enum IndicatorKind {
|
||||
Dot,
|
||||
Bar,
|
||||
Icon(AnyIcon),
|
||||
Character(char),
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
@@ -38,18 +37,12 @@ impl Indicator {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn character(character: char) -> Self {
|
||||
Self {
|
||||
kind: IndicatorKind::Character(character),
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Indicator {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let container = div().flex_none();
|
||||
@@ -67,9 +60,6 @@ impl RenderOnce for Indicator {
|
||||
.h_1p5()
|
||||
.rounded_t_md()
|
||||
.bg(self.color.color(cx)),
|
||||
IndicatorKind::Character(character) => container
|
||||
.text_color(self.color.color(cx))
|
||||
.child(character.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,40 +98,32 @@ impl Vim {
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_decimal_string(num: &str, delta: i64) -> String {
|
||||
let (negative, delta, num_str) = match num.strip_prefix('-') {
|
||||
Some(n) => (true, -delta, n),
|
||||
None => (false, delta, num),
|
||||
};
|
||||
let num_length = num_str.len();
|
||||
let leading_zero = num_str.starts_with('0');
|
||||
|
||||
let (result, new_negative) = match u64::from_str_radix(num_str, 10) {
|
||||
Ok(value) => {
|
||||
let wrapped = value.wrapping_add_signed(delta);
|
||||
if delta < 0 && wrapped > value {
|
||||
((u64::MAX - wrapped).wrapping_add(1), !negative)
|
||||
} else if delta > 0 && wrapped < value {
|
||||
(u64::MAX - wrapped, !negative)
|
||||
} else {
|
||||
(wrapped, negative)
|
||||
}
|
||||
fn increment_decimal_string(mut num: &str, mut delta: i64) -> String {
|
||||
let mut negative = false;
|
||||
if num.chars().next() == Some('-') {
|
||||
negative = true;
|
||||
delta = 0 - delta;
|
||||
num = &num[1..];
|
||||
}
|
||||
let result = if let Ok(value) = u64::from_str_radix(num, 10) {
|
||||
let wrapped = value.wrapping_add_signed(delta);
|
||||
if delta < 0 && wrapped > value {
|
||||
negative = !negative;
|
||||
(u64::MAX - wrapped).wrapping_add(1)
|
||||
} else if delta > 0 && wrapped < value {
|
||||
negative = !negative;
|
||||
u64::MAX - wrapped
|
||||
} else {
|
||||
wrapped
|
||||
}
|
||||
Err(_) => (u64::MAX, negative),
|
||||
} else {
|
||||
u64::MAX
|
||||
};
|
||||
|
||||
let formatted = format!("{}", result);
|
||||
let new_significant_digits = formatted.len();
|
||||
let padding = if leading_zero {
|
||||
num_length.saturating_sub(new_significant_digits)
|
||||
if result == 0 || !negative {
|
||||
format!("{}", result)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if new_negative && result != 0 {
|
||||
format!("-{}{}", "0".repeat(padding), formatted)
|
||||
} else {
|
||||
format!("{}{}", "0".repeat(padding), formatted)
|
||||
format!("-{}", result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,63 +286,6 @@ mod test {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_with_leading_zeros(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
000ˇ9
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes("ctrl-a").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
001ˇ0
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("2 ctrl-x").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
000ˇ8
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_with_leading_zeros_and_zero(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
01ˇ1
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes("ctrl-a").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
01ˇ2
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("1 2 ctrl-x").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
00ˇ0
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_with_changing_leading_zeros(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
099ˇ9
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes("ctrl-a").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
100ˇ0
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("2 ctrl-x").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
99ˇ8
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_with_two_dots(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
@@ -387,27 +322,6 @@ mod test {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_sign_change_with_leading_zeros(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.set_shared_state(indoc! {"
|
||||
00ˇ1
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("ctrl-x").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
00ˇ0
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("ctrl-x").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
-00ˇ1
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("2 ctrl-a").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
00ˇ1
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{"Put":{"state":"00ˇ1\n"}}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"00ˇ0\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"-00ˇ1\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"00ˇ1\n","mode":"Normal"}}
|
||||
@@ -1,6 +0,0 @@
|
||||
{"Put":{"state":"099ˇ9\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"100ˇ0\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"99ˇ8\n","mode":"Normal"}}
|
||||
@@ -1,6 +0,0 @@
|
||||
{"Put":{"state":"000ˇ9\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"001ˇ0\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"000ˇ8\n","mode":"Normal"}}
|
||||
@@ -1,7 +0,0 @@
|
||||
{"Put":{"state":"01ˇ1\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"01ˇ2\n","mode":"Normal"}}
|
||||
{"Key":"1"}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"00ˇ0\n","mode":"Normal"}}
|
||||
@@ -1,4 +0,0 @@
|
||||
{"Put":{"state":"001ˇ0\n"}}
|
||||
{"Key":"10"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"000ˇ9\n","mode":"Normal"}}
|
||||
@@ -1835,18 +1835,6 @@ impl Pane {
|
||||
focus_handle: &FocusHandle,
|
||||
cx: &mut ViewContext<'_, Pane>,
|
||||
) -> impl IntoElement {
|
||||
let pending_keystrokes = cx.pending_input_keystrokes().unwrap_or(&[]);
|
||||
let char_to_type = cx.bindings_for_action(&ActivateItem(ix)).iter().find_map(
|
||||
|keybinding| match keybinding.remaining_keystrokes(pending_keystrokes) {
|
||||
Some([keystroke])
|
||||
if keystroke.modifiers == cx.modifiers() && keystroke.key.len() == 1 =>
|
||||
{
|
||||
keystroke.key.chars().next()
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
let project_path = item.project_path(cx);
|
||||
|
||||
let is_active = ix == self.active_item_index;
|
||||
@@ -1878,7 +1866,7 @@ impl Pane {
|
||||
|
||||
let icon = item.tab_icon(cx);
|
||||
let close_side = &ItemSettings::get_global(cx).close_position;
|
||||
let indicator = render_item_indicator(item.boxed_clone(), char_to_type, cx);
|
||||
let indicator = render_item_indicator(item.boxed_clone(), cx);
|
||||
let item_id = item.item_id();
|
||||
let is_first_item = ix == 0;
|
||||
let is_last_item = ix == self.items.len() - 1;
|
||||
@@ -3009,22 +2997,16 @@ pub fn tab_details(items: &[Box<dyn ItemHandle>], cx: &AppContext) -> Vec<usize>
|
||||
tab_details
|
||||
}
|
||||
|
||||
pub fn render_item_indicator(
|
||||
item: Box<dyn ItemHandle>,
|
||||
char_to_type: Option<char>,
|
||||
cx: &WindowContext,
|
||||
) -> Option<Indicator> {
|
||||
if let Some(char) = char_to_type {
|
||||
return Some(Indicator::character(char));
|
||||
}
|
||||
pub fn render_item_indicator(item: Box<dyn ItemHandle>, cx: &WindowContext) -> Option<Indicator> {
|
||||
maybe!({
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Color::Warning,
|
||||
(_, true) => Color::Accent,
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Color::Warning,
|
||||
(_, true) => Color::Accent,
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
Some(Indicator::dot().color(indicator_color))
|
||||
Some(Indicator::dot().color(indicator_color))
|
||||
})
|
||||
}
|
||||
|
||||
impl Render for DraggedTab {
|
||||
|
||||
@@ -32,7 +32,6 @@ Zed supports hundreds of programming languages and text formats. Some work out-o
|
||||
- [JavaScript](./languages/javascript.md)
|
||||
- [Julia](./languages/julia.md)
|
||||
- [JSON](./languages/json.md)
|
||||
- [Jsonnet](./languages/jsonnet.md)
|
||||
- [Kotlin](./languages/kotlin.md)
|
||||
- [Lua](./languages/lua.md)
|
||||
- [Luau](./languages/luau.md)
|
||||
@@ -105,6 +104,7 @@ Zed supports hundreds of programming languages and text formats. Some work out-o
|
||||
- [Groq](https://github.com/juice49/zed-groq)
|
||||
- [INI](https://github.com/bajrangCoder/zed-ini)
|
||||
- [Java](https://github.com/zed-extensions/java)
|
||||
- [Jsonnet](https://github.com/narqo/zed-jsonnet)
|
||||
- [Justfiles](https://github.com/jackTabsCode/zed-just)
|
||||
- [LaTeX](https://github.com/rzukic/zed-latex)
|
||||
- [Ledger](https://github.com/mrkstwrt/zed-ledger)
|
||||
|
||||
@@ -10,7 +10,9 @@ Both use:
|
||||
- Tree Sitter: [tree-sitter/tree-sitter-java](https://github.com/tree-sitter/tree-sitter-java)
|
||||
- Language Server: [eclipse-jdtls/eclipse.jdt.ls](https://github.com/eclipse-jdtls/eclipse.jdt.ls)
|
||||
|
||||
## Install OpenJDK
|
||||
## Pre-requisites
|
||||
|
||||
### Install OpenJDK
|
||||
|
||||
You will need to install a Java runtime (OpenJDK).
|
||||
|
||||
@@ -21,19 +23,30 @@ You will need to install a Java runtime (OpenJDK).
|
||||
|
||||
Or manually download and install [OpenJDK 23](https://jdk.java.net/23/).
|
||||
|
||||
### (Optional) Install JDTLS
|
||||
|
||||
If you are using Java with Eclipse JDTLS, you can skip this section as it will automatically download a binary for you.
|
||||
|
||||
If you are using Zed Java you need to install your own copy of Eclipse JDT Language Server (`eclipse.jdt.ls`).
|
||||
|
||||
- MacOS: `brew install jdtls`
|
||||
- Arch: [`jdtls` from AUR](https://aur.archlinux.org/packages/jdtls)
|
||||
|
||||
Or manually download install:
|
||||
|
||||
- [JDTLS Milestone Builds](http://download.eclipse.org/jdtls/milestones/) (updated every two weeks)
|
||||
- [JDTLS Snapshot Builds](https://download.eclipse.org/jdtls/snapshots/) (frequent updates)
|
||||
|
||||
## Extension Install
|
||||
|
||||
You can install either by opening {#action zed::Extensions}({#kb zed::Extensions}) and searching for `java`.
|
||||
|
||||
We recommend you install one or the other and not both.
|
||||
|
||||
## Settings / Initialization Options
|
||||
|
||||
Both extensions will automatically download the language server, see: [Manual JDTLS Install](#manual-jdts-install) below if you'd prefer to manage that yourself.
|
||||
See [JDTLS Language Server Settings & Capabilities](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Language-Server-Settings-&-Capabilities) for a complete list of settings.
|
||||
|
||||
For available `initialization_options` please see the [Initialize Request section of the Eclipse.jdt.ls Wiki](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request).
|
||||
|
||||
You can add these customizations to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}) or by using a `.zed/setting.json` inside your project.
|
||||
Add the following to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}).
|
||||
|
||||
### Zed Java Settings
|
||||
|
||||
@@ -41,10 +54,7 @@ You can add these customizations to your Zed Settings by launching {#action zed:
|
||||
{
|
||||
"lsp": {
|
||||
"jdtls": {
|
||||
"settings": {
|
||||
"version": "1.40.0", // jdtls version to download and use
|
||||
"classpath": "/path/to/classes.jar:/path/to/more/classes/"
|
||||
},
|
||||
"settings": {},
|
||||
"initialization_options": {}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +74,36 @@ You can add these customizations to your Zed Settings by launching {#action zed:
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Zed Java Readme](https://github.com/zed-extensions/java)
|
||||
- [Java with Eclipse JDTLS Readme](https://github.com/ABckh/zed-java-eclipse-jdtls)
|
||||
|
||||
## Support
|
||||
|
||||
If you have issues with either of these plugins, please open issues on their respective repositories:
|
||||
|
||||
- [Zed Java Issues](https://github.com/zed-extensions/java/issues)
|
||||
- [Java with Eclipse JDTLS Issues](https://github.com/ABckh/zed-java-eclipse-jdtls/issues)
|
||||
|
||||
## Example Configs
|
||||
|
||||
### Zed Java Classpath
|
||||
|
||||
You can optionally configure the class path that JDTLS uses with:
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
"jdtls": {
|
||||
"settings": {
|
||||
"classpath": "/path/to/classes.jar:/path/to/more/classes/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Zed Java Initialization Options
|
||||
|
||||
There are also many more options you can pass directly to the language server, for example:
|
||||
@@ -166,27 +204,3 @@ For example, to enable [Lombok Support](https://github.com/redhat-developer/vsco
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Manual JDTLS Install
|
||||
|
||||
If you prefer, you can install JDTLS yourself and both extensions can be configured to use that instead.
|
||||
|
||||
- MacOS: `brew install jdtls`
|
||||
- Arch: [`jdtls` from AUR](https://aur.archlinux.org/packages/jdtls)
|
||||
|
||||
Or manually download install:
|
||||
|
||||
- [JDTLS Milestone Builds](http://download.eclipse.org/jdtls/milestones/) (updated every two weeks)
|
||||
- [JDTLS Snapshot Builds](https://download.eclipse.org/jdtls/snapshots/) (frequent updates)
|
||||
|
||||
## See also
|
||||
|
||||
- [Zed Java Readme](https://github.com/zed-extensions/java)
|
||||
- [Java with Eclipse JDTLS Readme](https://github.com/ABckh/zed-java-eclipse-jdtls)
|
||||
|
||||
## Support
|
||||
|
||||
If you have issues with either of these plugins, please open issues on their respective repositories:
|
||||
|
||||
- [Zed Java Issues](https://github.com/zed-extensions/java/issues)
|
||||
- [Java with Eclipse JDTLS Issues](https://github.com/ABckh/zed-java-eclipse-jdtls/issues)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Jsonnet
|
||||
|
||||
Jsonnet language support in Zed is provided by the community-maintained [Jsonnet extension](https://github.com/narqo/zed-jsonnet).
|
||||
|
||||
- Tree Sitter: [sourcegraph/tree-sitter-jsonnet](https://github.com/sourcegraph/tree-sitter-jsonnet)
|
||||
- Language Server: [grafana/jsonnet-language-server](https://github.com/grafana/jsonnet-language-server)
|
||||
|
||||
## Configuration
|
||||
|
||||
Workspace configuration options can be passed to the language server via the `lsp` settings of the `settings.json`.
|
||||
|
||||
The following example enables support for resolving [tanka](https://tanka.dev) import paths in `jsonnet-language-server`:
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
"jsonnet-language-server": {
|
||||
"settings": {
|
||||
"resolve_paths_with_tanka": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_elixir"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "elixir"
|
||||
name = "Elixir"
|
||||
description = "Elixir support."
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
schema_version = 1
|
||||
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
@@ -3,16 +3,6 @@
|
||||
(arguments (alias) @name)
|
||||
(#match? @context "^(defmodule|defprotocol)$")) @item
|
||||
|
||||
(call
|
||||
target: (identifier) @context
|
||||
(arguments (_) @name)?
|
||||
(#match? @context "^(setup|setup_all)$")) @item
|
||||
|
||||
(call
|
||||
target: (identifier) @context
|
||||
(arguments (string) @name)
|
||||
(#match? @context "^(describe|test)$")) @item
|
||||
|
||||
(unary_operator
|
||||
operator: "@" @name
|
||||
operand: (call
|
||||
|
||||
3
extensions/svelte/.gitignore
vendored
Normal file
3
extensions/svelte/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
target
|
||||
*.wasm
|
||||
grammars
|
||||
16
extensions/svelte/Cargo.toml
Normal file
16
extensions/svelte/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "zed_svelte"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/svelte.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.1.0"
|
||||
1
extensions/svelte/LICENSE-APACHE
Symbolic link
1
extensions/svelte/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
15
extensions/svelte/extension.toml
Normal file
15
extensions/svelte/extension.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
id = "svelte"
|
||||
name = "Svelte"
|
||||
description = "Svelte support"
|
||||
version = "0.2.0"
|
||||
schema_version = 1
|
||||
authors = []
|
||||
repository = "https://github.com/zed-extensions/svelte"
|
||||
|
||||
[language_servers.svelte-language-server]
|
||||
name = "Svelte Language Server"
|
||||
language = "Svelte"
|
||||
|
||||
[grammars.svelte]
|
||||
repository = "https://github.com/tree-sitter-grammars/tree-sitter-svelte"
|
||||
commit = "3f06f705410683adb17d146b5eca28c62fe81ba6"
|
||||
7
extensions/svelte/languages/svelte/brackets.scm
Normal file
7
extensions/svelte/languages/svelte/brackets.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
("<" @open ">" @close)
|
||||
("{" @open "}" @close)
|
||||
("'" @open "'" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("(" @open ")" @close)
|
||||
; ("[" @open "]" @close)
|
||||
; ("`" @open "`" @close)
|
||||
22
extensions/svelte/languages/svelte/config.toml
Normal file
22
extensions/svelte/languages/svelte/config.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
name = "Svelte"
|
||||
grammar = "svelte"
|
||||
path_suffixes = ["svelte"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
autoclose_before = ":\"'}]>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "<", end = ">", close = true, newline = true, not_in = ["string"] },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "!--", end = " --", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = true, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = true, not_in = ["string"] },
|
||||
{ start = "`", end = "`", close = true, newline = true, not_in = ["string"] },
|
||||
]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "svelte"
|
||||
prettier_plugins = ["prettier-plugin-svelte"]
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
opt_into_language_servers = ["tailwindcss-language-server"]
|
||||
107
extensions/svelte/languages/svelte/highlights.scm
Executable file
107
extensions/svelte/languages/svelte/highlights.scm
Executable file
@@ -0,0 +1,107 @@
|
||||
|
||||
; comments
|
||||
(comment) @comment
|
||||
|
||||
; property attribute
|
||||
(attribute_directive) @attribute.function
|
||||
(attribute_identifier) @attribute
|
||||
(attribute_modifier) @attribute.special
|
||||
|
||||
; Style component attributes as @property
|
||||
(start_tag
|
||||
(
|
||||
(tag_name) @_tag_name
|
||||
(#match? @_tag_name "^[A-Z]")
|
||||
)
|
||||
(attribute
|
||||
(attribute_name
|
||||
(attribute_identifier) @tag.property
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(self_closing_tag
|
||||
(
|
||||
(tag_name) @_tag_name
|
||||
(#match? @_tag_name "^[A-Z]")
|
||||
)
|
||||
(attribute
|
||||
(attribute_name
|
||||
(attribute_identifier) @tag.property
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; style elements starting with lowercase letters as tags
|
||||
(
|
||||
(tag_name) @tag
|
||||
(#match? @tag "^[a-z]")
|
||||
)
|
||||
|
||||
; style elements starting with uppercase letters as components (types)
|
||||
; Also valid might be to treat them as constructors
|
||||
(
|
||||
(tag_name) @tag @tag.component.type.constructor
|
||||
(#match? @tag "^[A-Z]")
|
||||
)
|
||||
|
||||
[
|
||||
"<"
|
||||
">"
|
||||
"</"
|
||||
"/>"
|
||||
] @tag.punctuation.bracket
|
||||
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"|"
|
||||
] @punctuation.delimiter
|
||||
|
||||
|
||||
[
|
||||
"@"
|
||||
"#"
|
||||
":"
|
||||
"/"
|
||||
] @tag.punctuation.special
|
||||
|
||||
"=" @operator
|
||||
|
||||
|
||||
; Treating (if, each, ...) as a keyword inside of blocks
|
||||
; like {#if ...} or {#each ...}
|
||||
(block_start_tag
|
||||
tag: _ @tag.keyword
|
||||
)
|
||||
|
||||
(block_tag
|
||||
tag: _ @tag.keyword
|
||||
)
|
||||
|
||||
(block_end_tag
|
||||
tag: _ @tag.keyword
|
||||
)
|
||||
|
||||
(expression_tag
|
||||
tag: _ @tag.keyword
|
||||
)
|
||||
|
||||
; Style quoted string attribute values
|
||||
(quoted_attribute_value) @string
|
||||
|
||||
|
||||
; Highlight the `as` keyword in each blocks
|
||||
(each_start
|
||||
("as") @tag.keyword
|
||||
)
|
||||
|
||||
|
||||
; Highlight the snippet name as a function
|
||||
; (e.g. {#snippet foo(bar)}
|
||||
(snippet_name) @function
|
||||
9
extensions/svelte/languages/svelte/indents.scm
Executable file
9
extensions/svelte/languages/svelte/indents.scm
Executable file
@@ -0,0 +1,9 @@
|
||||
[
|
||||
(element)
|
||||
(if_statement)
|
||||
(each_statement)
|
||||
(await_statement)
|
||||
(snippet_statement)
|
||||
(script_element)
|
||||
(style_element)
|
||||
] @indent
|
||||
86
extensions/svelte/languages/svelte/injections.scm
Executable file
86
extensions/svelte/languages/svelte/injections.scm
Executable file
@@ -0,0 +1,86 @@
|
||||
; ; injections.scm
|
||||
; ; --------------
|
||||
|
||||
; Match script tags with a lang attribute
|
||||
(script_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_attr_name
|
||||
(#eq? @_attr_name "lang")
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @language
|
||||
)
|
||||
)
|
||||
)
|
||||
(raw_text) @content
|
||||
)
|
||||
|
||||
; Match script tags without a lang attribute
|
||||
(script_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_attr_name
|
||||
)*
|
||||
)
|
||||
(raw_text) @content
|
||||
(#not-any-of? @_attr_name "lang")
|
||||
(#set! language "javascript")
|
||||
)
|
||||
|
||||
; Match the contents of the script's generics="T extends string" as typescript code
|
||||
;
|
||||
; Disabled for the time-being because tree-sitter is treating the generics
|
||||
; attribute as a top-level typescript statement, where `T extends string` is
|
||||
; not a valid top-level typescript statement.
|
||||
;
|
||||
; (script_element
|
||||
; (start_tag
|
||||
; (attribute
|
||||
; (attribute_name) @_attr_name
|
||||
; (#eq? @_attr_name "generics")
|
||||
; (quoted_attribute_value
|
||||
; (attribute_value) @content
|
||||
; )
|
||||
; )
|
||||
; )
|
||||
; (#set! language "typescript")
|
||||
; )
|
||||
|
||||
|
||||
; Mark everything as typescript because it's
|
||||
; a more generic superset of javascript
|
||||
; Not sure if it's possible to somehow refer to the
|
||||
; script's language attribute here.
|
||||
((svelte_raw_text) @content
|
||||
(#set! "language" "ts")
|
||||
)
|
||||
|
||||
; Match style tags with a lang attribute
|
||||
(style_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_attr_name
|
||||
(#eq? @_attr_name "lang")
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @language
|
||||
)
|
||||
)
|
||||
)
|
||||
(raw_text) @content
|
||||
)
|
||||
|
||||
; Match style tags without a lang attribute
|
||||
(style_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_attr_name
|
||||
)*
|
||||
)
|
||||
(raw_text) @content
|
||||
(#not-any-of? @_attr_name "lang")
|
||||
(#set! language "css")
|
||||
)
|
||||
|
||||
|
||||
; Downstream TODO: Style highlighting for `style:background="red"` and `style="background: red"` strings
|
||||
; Downstream TODO: Style component comments as markdown
|
||||
69
extensions/svelte/languages/svelte/outline.scm
Normal file
69
extensions/svelte/languages/svelte/outline.scm
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
(script_element
|
||||
(start_tag) @name
|
||||
(raw_text) @context @item
|
||||
)
|
||||
|
||||
(script_element
|
||||
(end_tag) @name @item
|
||||
)
|
||||
|
||||
(style_element
|
||||
(start_tag) @name
|
||||
(raw_text) @context
|
||||
) @item
|
||||
|
||||
|
||||
(document) @item
|
||||
|
||||
(comment) @annotation
|
||||
|
||||
(if_statement
|
||||
(if_start) @name
|
||||
) @item
|
||||
|
||||
(else_block
|
||||
(else_start) @name
|
||||
) @item
|
||||
|
||||
(else_if_block
|
||||
(else_if_start) @name
|
||||
) @item
|
||||
|
||||
(element
|
||||
(start_tag) @name
|
||||
) @item
|
||||
|
||||
(element
|
||||
(self_closing_tag) @name
|
||||
) @item
|
||||
|
||||
|
||||
; (if_end) @name @item
|
||||
|
||||
(each_statement
|
||||
(each_start) @name
|
||||
) @item
|
||||
|
||||
|
||||
(snippet_statement
|
||||
(snippet_start) @name
|
||||
) @item
|
||||
|
||||
(snippet_end) @name @item
|
||||
|
||||
(html_tag) @name @item
|
||||
|
||||
(const_tag) @name @item
|
||||
|
||||
(await_statement
|
||||
(await_start) @name
|
||||
) @item
|
||||
|
||||
(then_block
|
||||
(then_start) @name
|
||||
) @item
|
||||
|
||||
(catch_block
|
||||
(catch_start) @name
|
||||
) @item
|
||||
7
extensions/svelte/languages/svelte/overrides.scm
Normal file
7
extensions/svelte/languages/svelte/overrides.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(raw_text)
|
||||
(attribute_value)
|
||||
(quoted_attribute_value)
|
||||
] @string
|
||||
124
extensions/svelte/src/svelte.rs
Normal file
124
extensions/svelte/src/svelte.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::{env, fs};
|
||||
use zed_extension_api::{self as zed, serde_json, Result};
|
||||
|
||||
struct SvelteExtension {
|
||||
did_find_server: bool,
|
||||
}
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/svelte-language-server/bin/server.js";
|
||||
const PACKAGE_NAME: &str = "svelte-language-server";
|
||||
|
||||
impl SvelteExtension {
|
||||
fn server_exists(&self) -> bool {
|
||||
fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
|
||||
}
|
||||
|
||||
fn server_script_path(&mut self, id: &zed::LanguageServerId) -> Result<String> {
|
||||
let server_exists = self.server_exists();
|
||||
if self.did_find_server && server_exists {
|
||||
return Ok(SERVER_PATH.to_string());
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
id,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
);
|
||||
let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
|
||||
|
||||
if !server_exists
|
||||
|| zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
|
||||
{
|
||||
zed::set_language_server_installation_status(
|
||||
id,
|
||||
&zed::LanguageServerInstallationStatus::Downloading,
|
||||
);
|
||||
let result = zed::npm_install_package(PACKAGE_NAME, &version);
|
||||
match result {
|
||||
Ok(()) => {
|
||||
if !self.server_exists() {
|
||||
Err(format!(
|
||||
"installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.server_exists() {
|
||||
Err(error)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.did_find_server = true;
|
||||
Ok(SERVER_PATH.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl zed::Extension for SvelteExtension {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
did_find_server: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
id: &zed::LanguageServerId,
|
||||
_: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let server_path = self.server_script_path(id)?;
|
||||
Ok(zed::Command {
|
||||
command: zed::node_binary_path()?,
|
||||
args: vec![
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.join(&server_path)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
"--stdio".to_string(),
|
||||
],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn language_server_initialization_options(
|
||||
&mut self,
|
||||
_: &zed::LanguageServerId,
|
||||
_: &zed::Worktree,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let config = serde_json::json!({
|
||||
"inlayHints": {
|
||||
"parameterNames": {
|
||||
"enabled": "all",
|
||||
"suppressWhenArgumentMatchesName": false
|
||||
},
|
||||
"parameterTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"variableTypes": {
|
||||
"enabled": true,
|
||||
"suppressWhenTypeMatchesName": false
|
||||
},
|
||||
"propertyDeclarationTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"functionLikeReturnTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"enumMemberValues": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Some(serde_json::json!({
|
||||
"provideFormatter": true,
|
||||
"configuration": {
|
||||
"typescript": config,
|
||||
"javascript": config
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(SvelteExtension);
|
||||
17
extensions/vue/Cargo.toml
Normal file
17
extensions/vue/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "zed_vue"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/vue.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
zed_extension_api = "0.1.0"
|
||||
1
extensions/vue/LICENSE-APACHE
Symbolic link
1
extensions/vue/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
19
extensions/vue/extension.toml
Normal file
19
extensions/vue/extension.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
id = "vue"
|
||||
name = "Vue"
|
||||
description = "Vue support."
|
||||
version = "0.1.0"
|
||||
schema_version = 1
|
||||
authors = ["Piotr Osiewicz <piotr@zed.dev>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
[language_servers.vue-language-server]
|
||||
name = "Vue Language Server"
|
||||
language = "Vue.js"
|
||||
language_ids = { "Vue.js" = "vue" }
|
||||
# REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it
|
||||
# sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec.
|
||||
code_action_kinds = ["", "quickfix", "refactor.rewrite"]
|
||||
|
||||
[grammars.vue]
|
||||
repository = "https://github.com/tree-sitter-grammars/tree-sitter-vue"
|
||||
commit = "7e48557b903a9db9c38cea3b7839ef7e1f36c693"
|
||||
2
extensions/vue/languages/vue/brackets.scm
Normal file
2
extensions/vue/languages/vue/brackets.scm
Normal file
@@ -0,0 +1,2 @@
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
22
extensions/vue/languages/vue/config.toml
Normal file
22
extensions/vue/languages/vue/config.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
name = "Vue.js"
|
||||
code_fence_block_name = "vue"
|
||||
grammar = "vue"
|
||||
path_suffixes = ["vue"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
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 = true, not_in = ["string", "comment"] },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "`", end = "`", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
word_characters = ["-"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "vue"
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
opt_into_language_servers = ["tailwindcss-language-server"]
|
||||
15
extensions/vue/languages/vue/highlights.scm
Normal file
15
extensions/vue/languages/vue/highlights.scm
Normal file
@@ -0,0 +1,15 @@
|
||||
(attribute) @property
|
||||
(directive_attribute) @property
|
||||
(quoted_attribute_value) @string
|
||||
(interpolation) @punctuation.special
|
||||
(raw_text) @embedded
|
||||
|
||||
((tag_name) @type
|
||||
(#match? @type "^[A-Z]"))
|
||||
|
||||
(directive_name) @keyword
|
||||
(directive_argument) @constant
|
||||
|
||||
(start_tag) @tag
|
||||
(end_tag) @tag
|
||||
(self_closing_tag) @tag
|
||||
60
extensions/vue/languages/vue/injections.scm
Normal file
60
extensions/vue/languages/vue/injections.scm
Normal file
@@ -0,0 +1,60 @@
|
||||
; <script>
|
||||
((script_element
|
||||
(start_tag) @_no_lang
|
||||
(raw_text) @content)
|
||||
(#not-match? @_no_lang "lang=")
|
||||
(#set! "language" "javascript"))
|
||||
|
||||
; <script lang="js">
|
||||
((script_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_lang
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @_js)))
|
||||
(raw_text) @content)
|
||||
(#eq? @_lang "lang")
|
||||
(#eq? @_js "js")
|
||||
(#set! "language" "javascript"))
|
||||
|
||||
; <script lang="ts">
|
||||
((script_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_lang
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @_ts)))
|
||||
(raw_text) @content)
|
||||
(#eq? @_lang "lang")
|
||||
(#eq? @_ts "ts")
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
; <script lang="tsx">
|
||||
; <script lang="jsx">
|
||||
; Zed built-in tsx, we mark it as tsx ^:)
|
||||
(script_element
|
||||
(start_tag
|
||||
(attribute
|
||||
(attribute_name) @_attr
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @language)))
|
||||
(#eq? @_attr "lang")
|
||||
(#any-of? @language "tsx" "jsx")
|
||||
(raw_text) @content)
|
||||
|
||||
|
||||
; {{ }}
|
||||
((interpolation
|
||||
(raw_text) @content)
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
; v-
|
||||
(directive_attribute
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @content
|
||||
(#set! "language" "typescript")))
|
||||
|
||||
; TODO: support less/sass/scss
|
||||
(style_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "css"))
|
||||
7
extensions/vue/languages/vue/overrides.scm
Normal file
7
extensions/vue/languages/vue/overrides.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(raw_text)
|
||||
(attribute_value)
|
||||
(quoted_attribute_value)
|
||||
] @string
|
||||
205
extensions/vue/src/vue.rs
Normal file
205
extensions/vue/src/vue.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{env, fs};
|
||||
|
||||
use serde::Deserialize;
|
||||
use zed::lsp::{Completion, CompletionKind};
|
||||
use zed::CodeLabelSpan;
|
||||
use zed_extension_api::{self as zed, serde_json, Result};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/@vue/language-server/bin/vue-language-server.js";
|
||||
const PACKAGE_NAME: &str = "@vue/language-server";
|
||||
|
||||
const TYPESCRIPT_PACKAGE_NAME: &str = "typescript";
|
||||
|
||||
/// The relative path to TypeScript's SDK.
|
||||
const TYPESCRIPT_TSDK_PATH: &str = "node_modules/typescript/lib";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PackageJson {
|
||||
#[serde(default)]
|
||||
dependencies: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
dev_dependencies: HashMap<String, String>,
|
||||
}
|
||||
|
||||
struct VueExtension {
|
||||
did_find_server: bool,
|
||||
typescript_tsdk_path: String,
|
||||
}
|
||||
|
||||
impl VueExtension {
|
||||
fn server_exists(&self) -> bool {
|
||||
fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
|
||||
}
|
||||
|
||||
fn server_script_path(
|
||||
&mut self,
|
||||
language_server_id: &zed::LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
let server_exists = self.server_exists();
|
||||
if self.did_find_server && server_exists {
|
||||
self.install_typescript_if_needed(worktree)?;
|
||||
return Ok(SERVER_PATH.to_string());
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
language_server_id,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
);
|
||||
// We hardcode the version to 1.8 since we do not support @vue/language-server 2.0 yet.
|
||||
let version = "1.8".to_string();
|
||||
|
||||
if !server_exists
|
||||
|| zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
|
||||
{
|
||||
zed::set_language_server_installation_status(
|
||||
language_server_id,
|
||||
&zed::LanguageServerInstallationStatus::Downloading,
|
||||
);
|
||||
let result = zed::npm_install_package(PACKAGE_NAME, &version);
|
||||
match result {
|
||||
Ok(()) => {
|
||||
if !self.server_exists() {
|
||||
Err(format!(
|
||||
"installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.server_exists() {
|
||||
Err(error)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.install_typescript_if_needed(worktree)?;
|
||||
self.did_find_server = true;
|
||||
Ok(SERVER_PATH.to_string())
|
||||
}
|
||||
|
||||
/// Returns whether a local copy of TypeScript exists in the worktree.
|
||||
fn typescript_exists_for_worktree(&self, worktree: &zed::Worktree) -> Result<bool> {
|
||||
let package_json = worktree.read_text_file("package.json")?;
|
||||
let package_json: PackageJson = serde_json::from_str(&package_json)
|
||||
.map_err(|err| format!("failed to parse package.json: {err}"))?;
|
||||
|
||||
let dev_dependencies = &package_json.dev_dependencies;
|
||||
let dependencies = &package_json.dependencies;
|
||||
|
||||
// Since the extension is not allowed to read the filesystem within the project
|
||||
// except through the worktree (which does not contains `node_modules`), we check
|
||||
// the `package.json` to see if `typescript` is listed in the dependencies.
|
||||
Ok(dev_dependencies.contains_key(TYPESCRIPT_PACKAGE_NAME)
|
||||
|| dependencies.contains_key(TYPESCRIPT_PACKAGE_NAME))
|
||||
}
|
||||
|
||||
fn install_typescript_if_needed(&mut self, worktree: &zed::Worktree) -> Result<()> {
|
||||
if self
|
||||
.typescript_exists_for_worktree(worktree)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
println!("found local TypeScript installation at '{TYPESCRIPT_TSDK_PATH}'");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let installed_typescript_version =
|
||||
zed::npm_package_installed_version(TYPESCRIPT_PACKAGE_NAME)?;
|
||||
let latest_typescript_version = zed::npm_package_latest_version(TYPESCRIPT_PACKAGE_NAME)?;
|
||||
|
||||
if installed_typescript_version.as_ref() != Some(&latest_typescript_version) {
|
||||
println!("installing {TYPESCRIPT_PACKAGE_NAME}@{latest_typescript_version}");
|
||||
zed::npm_install_package(TYPESCRIPT_PACKAGE_NAME, &latest_typescript_version)?;
|
||||
} else {
|
||||
println!("typescript already installed");
|
||||
}
|
||||
|
||||
self.typescript_tsdk_path = env::current_dir()
|
||||
.unwrap()
|
||||
.join(TYPESCRIPT_TSDK_PATH)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl zed::Extension for VueExtension {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
did_find_server: false,
|
||||
typescript_tsdk_path: TYPESCRIPT_TSDK_PATH.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
language_server_id: &zed::LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let server_path = self.server_script_path(language_server_id, worktree)?;
|
||||
Ok(zed::Command {
|
||||
command: zed::node_binary_path()?,
|
||||
args: vec![
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.join(&server_path)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
"--stdio".to_string(),
|
||||
],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn language_server_initialization_options(
|
||||
&mut self,
|
||||
_language_server_id: &zed::LanguageServerId,
|
||||
_worktree: &zed::Worktree,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(serde_json::json!({
|
||||
"typescript": {
|
||||
"tsdk": self.typescript_tsdk_path
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
fn label_for_completion(
|
||||
&self,
|
||||
_language_server_id: &zed::LanguageServerId,
|
||||
completion: Completion,
|
||||
) -> Option<zed::CodeLabel> {
|
||||
let highlight_name = match completion.kind? {
|
||||
CompletionKind::Class | CompletionKind::Interface => "type",
|
||||
CompletionKind::Constructor => "type",
|
||||
CompletionKind::Constant => "constant",
|
||||
CompletionKind::Function | CompletionKind::Method => "function",
|
||||
CompletionKind::Property | CompletionKind::Field => "tag",
|
||||
CompletionKind::Variable => "type",
|
||||
CompletionKind::Keyword => "keyword",
|
||||
CompletionKind::Value => "tag",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let len = completion.label.len();
|
||||
let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
|
||||
|
||||
Some(zed::CodeLabel {
|
||||
code: Default::default(),
|
||||
spans: if let Some(detail) = completion.detail {
|
||||
vec![
|
||||
name_span,
|
||||
CodeLabelSpan::literal(" ", None),
|
||||
CodeLabelSpan::literal(detail, None),
|
||||
]
|
||||
} else {
|
||||
vec![name_span]
|
||||
},
|
||||
filter_range: (0..len).into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(VueExtension);
|
||||
Reference in New Issue
Block a user