Compare commits

..

4 Commits

Author SHA1 Message Date
Piotr Osiewicz
217fc298ed Merge branch 'main' into language-toolchains 2024-10-18 14:26:50 +02:00
Piotr Osiewicz
cf304df0dc WIP 2024-10-17 13:27:12 +02:00
Piotr Osiewicz
7b8463b566 💄 2024-10-17 11:16:07 +02:00
Piotr Osiewicz
241a73df54 wip 2024-10-17 10:58:42 +02:00
111 changed files with 2313 additions and 2633 deletions

View File

@@ -14,7 +14,6 @@ on:
- "**" - "**"
paths-ignore: paths-ignore:
- "docs/**" - "docs/**"
- ".github/workflows/community_*"
concurrency: concurrency:
# Allow only one workflow per any non-`main` branch. # Allow only one workflow per any non-`main` branch.
@@ -138,7 +137,7 @@ jobs:
clean: false clean: false
- name: Cache dependencies - name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with: with:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet" cache-provider: "buildjet"
@@ -170,7 +169,7 @@ jobs:
clean: false clean: false
- name: Cache dependencies - name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with: with:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet" cache-provider: "buildjet"
@@ -193,7 +192,7 @@ jobs:
clean: false clean: false
- name: Cache dependencies - name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with: with:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github" cache-provider: "github"
@@ -267,20 +266,20 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg 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 - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-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 - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -331,7 +330,7 @@ jobs:
run: script/bundle-linux run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz 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 run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -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. 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! 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, # We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from # 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all # 'community' to 'zed' repository. The migration added activity to all

View File

@@ -21,7 +21,7 @@ jobs:
clean: false clean: false
- name: Cache dependencies - name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with: with:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github" cache-provider: "github"

30
Cargo.lock generated
View File

@@ -3649,12 +3649,6 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ec4rs"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf65d056c7da9c971c2847ce250fd1f0f9659d5718845c3ec0ad95f5668352c"
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.14.8" version = "0.14.8"
@@ -6216,7 +6210,6 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"ctor", "ctor",
"ec4rs",
"env_logger", "env_logger",
"futures 0.3.30", "futures 0.3.30",
"fuzzy", "fuzzy",
@@ -9126,7 +9119,6 @@ name = "remote"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"collections", "collections",
"fs", "fs",
"futures 0.3.30", "futures 0.3.30",
@@ -9311,7 +9303,6 @@ dependencies = [
"system-configuration 0.6.1", "system-configuration 0.6.1",
"tokio", "tokio",
"tokio-rustls 0.26.0", "tokio-rustls 0.26.0",
"tokio-socks",
"tokio-util", "tokio-util",
"tower-service", "tower-service",
"url", "url",
@@ -10309,7 +10300,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"ec4rs",
"fs", "fs",
"futures 0.3.30", "futures 0.3.30",
"gpui", "gpui",
@@ -12011,7 +12001,6 @@ dependencies = [
"futures-io", "futures-io",
"futures-util", "futures-util",
"thiserror", "thiserror",
"tokio",
] ]
[[package]] [[package]]
@@ -12604,11 +12593,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"editor", "editor",
"gpui", "gpui",
"menu",
"settings", "settings",
"theme", "theme",
"ui", "ui",
"workspace",
] ]
[[package]] [[package]]
@@ -14753,7 +14740,7 @@ dependencies = [
[[package]] [[package]]
name = "zed_elixir" name = "zed_elixir"
version = "0.1.1" version = "0.1.0"
dependencies = [ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
@@ -14877,6 +14864,13 @@ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
[[package]]
name = "zed_svelte"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]] [[package]]
name = "zed_terraform" name = "zed_terraform"
version = "0.1.1" version = "0.1.1"
@@ -14905,6 +14899,14 @@ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
[[package]]
name = "zed_vue"
version = "0.1.0"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
]
[[package]] [[package]]
name = "zed_zig" name = "zed_zig"
version = "0.3.1" version = "0.3.1"

View File

@@ -158,10 +158,12 @@ members = [
"extensions/ruff", "extensions/ruff",
"extensions/slash-commands-example", "extensions/slash-commands-example",
"extensions/snippets", "extensions/snippets",
"extensions/svelte",
"extensions/terraform", "extensions/terraform",
"extensions/test-extension", "extensions/test-extension",
"extensions/toml", "extensions/toml",
"extensions/uiua", "extensions/uiua",
"extensions/vue",
"extensions/zig", "extensions/zig",
# #
@@ -347,7 +349,6 @@ ctor = "0.2.6"
dashmap = "6.0" dashmap = "6.0"
derive_more = "0.99.17" derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
ec4rs = "1.1"
emojis = "0.6.1" emojis = "0.6.1"
env_logger = "0.11" env_logger = "0.11"
exec = "0.3.1" exec = "0.3.1"
@@ -390,14 +391,7 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
rand = "0.8.5" rand = "0.8.5"
regex = "1.5" regex = "1.5"
repair_json = "0.1.0" repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [ 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"]}
"charset",
"http2",
"macos-system-configuration",
"rustls-tls-native-roots",
"socks",
"stream",
] }
rsa = "0.9.6" rsa = "0.9.6"
runtimelib = { version = "0.15", default-features = false, features = [ runtimelib = { version = "0.15", default-features = false, features = [
"async-dispatcher-runtime", "async-dispatcher-runtime",

View File

@@ -128,7 +128,6 @@
"php": "php", "php": "php",
"plist": "template", "plist": "template",
"png": "image", "png": "image",
"postcss": "css",
"ppt": "document", "ppt": "document",
"pptx": "document", "pptx": "document",
"prettierignore": "prettier", "prettierignore": "prettier",

View File

@@ -34,7 +34,7 @@
"cmd-]": "pane::GoForward", "cmd-]": "pane::GoForward",
"alt-f7": "editor::FindAllReferences", "alt-f7": "editor::FindAllReferences",
"cmd-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-alt-b": "editor::GoToDefinitionSplit",
"cmd-shift-b": "editor::GoToTypeDefinition", "cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit", "cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
@@ -64,8 +64,7 @@
"cmd-shift-o": "file_finder::Toggle", "cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle", "cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle", "shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol "cmd-alt-o": "project_symbols::Toggle",
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-1": "workspace::ToggleLeftDock", "cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy" "cmd-6": "diagnostics::Deploy"
} }

View File

@@ -2219,7 +2219,6 @@ impl ContextEditor {
merge_adjacent: false, merge_adjacent: false,
}; };
let should_refold;
if let Some(state) = self.patches.get_mut(&range) { if let Some(state) = self.patches.get_mut(&range) {
replaced_blocks.insert(state.footer_block_id, render_block); replaced_blocks.insert(state.footer_block_id, render_block);
if let Some(editor_state) = &state.editor { if let Some(editor_state) = &state.editor {
@@ -2234,9 +2233,6 @@ impl ContextEditor {
}); });
} }
} }
should_refold =
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} else { } else {
let block_ids = editor.insert_blocks( let block_ids = editor.insert_blocks(
[BlockProperties { [BlockProperties {
@@ -2270,14 +2266,10 @@ impl ContextEditor {
update_task: None, update_task: None,
}, },
); );
should_refold = true;
} }
if should_refold { editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.unfold_ranges([patch_start..patch_end], true, false, cx); editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
}
} }
editor.remove_creases(removed_crease_ids, cx); editor.remove_creases(removed_crease_ids, cx);

View File

@@ -146,28 +146,12 @@ impl ResolvedEdit {
return false; return false;
} }
let other_offset_range = other_range.to_offset(buffer); if let Some(description) = &mut self.description {
let offset_range = range.to_offset(buffer); if let Some(other_description) = &other.description {
// If the other range is empty at the start of this edit's range, combine the new text
if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
self.new_text = format!("{}\n{}", other.new_text, self.new_text);
self.range.start = other_range.start;
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
*description = format!("{}\n{}", other_description, description)
}
} else {
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
description.push('\n'); description.push('\n');
description.push_str(other_description); description.push_str(other_description);
} }
} }
true true
} }
} }
@@ -715,73 +699,6 @@ mod tests {
.unindent(), .unindent(),
cx, cx,
); );
// Ensure InsertBefore merges correctly with Update of the same text
assert_edits(
"
fn foo() {
}
"
.unindent(),
vec![
AssistantEditKind::InsertBefore {
old_text: "
fn foo() {"
.unindent(),
new_text: "
fn bar() {
qux();
}"
.unindent(),
description: "implement bar".into(),
},
AssistantEditKind::Update {
old_text: "
fn foo() {
}"
.unindent(),
new_text: "
fn foo() {
bar();
}"
.unindent(),
description: "call bar in foo".into(),
},
AssistantEditKind::InsertAfter {
old_text: "
fn foo() {
}
"
.unindent(),
new_text: "
fn qux() {
// todo
}
"
.unindent(),
description: "implement qux".into(),
},
],
"
fn bar() {
qux();
}
fn foo() {
bar();
}
fn qux() {
// todo
}
"
.unindent(),
cx,
);
} }
#[track_caller] #[track_caller]

View File

@@ -78,10 +78,10 @@ CREATE TABLE "worktree_entries" (
"id" INTEGER NOT NULL, "id" INTEGER NOT NULL,
"is_dir" BOOL NOT NULL, "is_dir" BOOL NOT NULL,
"path" VARCHAR NOT NULL, "path" VARCHAR NOT NULL,
"canonical_path" TEXT,
"inode" INTEGER NOT NULL, "inode" INTEGER NOT NULL,
"mtime_seconds" INTEGER NOT NULL, "mtime_seconds" INTEGER NOT NULL,
"mtime_nanos" INTEGER NOT NULL, "mtime_nanos" INTEGER NOT NULL,
"is_symlink" BOOL NOT NULL,
"is_external" BOOL NOT NULL, "is_external" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL, "is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL, "is_deleted" BOOL NOT NULL,

View File

@@ -1,2 +0,0 @@
ALTER TABLE worktree_entries ADD COLUMN canonical_path text;
ALTER TABLE worktree_entries ALTER COLUMN is_symlink SET DEFAULT false;

View File

@@ -317,7 +317,7 @@ impl Database {
inode: ActiveValue::set(entry.inode as i64), inode: ActiveValue::set(entry.inode as i64),
mtime_seconds: ActiveValue::set(mtime.seconds as i64), mtime_seconds: ActiveValue::set(mtime.seconds as i64),
mtime_nanos: ActiveValue::set(mtime.nanos as i32), mtime_nanos: ActiveValue::set(mtime.nanos as i32),
canonical_path: ActiveValue::set(entry.canonical_path.clone()), is_symlink: ActiveValue::set(entry.is_symlink),
is_ignored: ActiveValue::set(entry.is_ignored), is_ignored: ActiveValue::set(entry.is_ignored),
is_external: ActiveValue::set(entry.is_external), is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)), git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
@@ -338,7 +338,7 @@ impl Database {
worktree_entry::Column::Inode, worktree_entry::Column::Inode,
worktree_entry::Column::MtimeSeconds, worktree_entry::Column::MtimeSeconds,
worktree_entry::Column::MtimeNanos, worktree_entry::Column::MtimeNanos,
worktree_entry::Column::CanonicalPath, worktree_entry::Column::IsSymlink,
worktree_entry::Column::IsIgnored, worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus, worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId, worktree_entry::Column::ScanId,
@@ -735,7 +735,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64, seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32, nanos: db_entry.mtime_nanos as u32,
}), }),
canonical_path: db_entry.canonical_path, is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored, is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external, is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32), git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -659,7 +659,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64, seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32, nanos: db_entry.mtime_nanos as u32,
}), }),
canonical_path: db_entry.canonical_path, is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored, is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external, is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32), git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -16,12 +16,12 @@ pub struct Model {
pub mtime_seconds: i64, pub mtime_seconds: i64,
pub mtime_nanos: i32, pub mtime_nanos: i32,
pub git_status: Option<i64>, pub git_status: Option<i64>,
pub is_symlink: bool,
pub is_ignored: bool, pub is_ignored: bool,
pub is_external: bool, pub is_external: bool,
pub is_deleted: bool, pub is_deleted: bool,
pub scan_id: i64, pub scan_id: i64,
pub is_fifo: bool, pub is_fifo: bool,
pub canonical_path: Option<String>,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -198,6 +198,10 @@ impl Config {
} }
} }
pub fn is_llm_billing_enabled(&self) -> bool {
self.stripe_api_key.is_some()
}
#[cfg(test)] #[cfg(test)]
pub fn test() -> Self { pub fn test() -> Self {
Self { Self {

View File

@@ -460,27 +460,29 @@ async fn check_usage_limit(
) )
.await?; .await?;
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT { if state.config.is_llm_billing_enabled() {
if !claims.has_llm_subscription { if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
return Err(Error::http( if !claims.has_llm_subscription {
StatusCode::PAYMENT_REQUIRED, return Err(Error::http(
"Maximum spending limit reached for this month.".to_string(), StatusCode::PAYMENT_REQUIRED,
)); "Maximum spending limit reached for this month.".to_string(),
} ));
}
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT) if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
>= Cents(claims.max_monthly_spend_in_cents) >= Cents(claims.max_monthly_spend_in_cents)
{ {
return Err(Error::Http( return Err(Error::Http(
StatusCode::FORBIDDEN, StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(), "Maximum spending limit reached for this month.".to_string(),
[( [(
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME), HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
HeaderValue::from_static("true"), HeaderValue::from_static("true"),
)] )]
.into_iter() .into_iter()
.collect(), .collect(),
)); ));
}
} }
} }
@@ -625,6 +627,7 @@ where
impl<S> Drop for TokenCountingStream<S> { impl<S> Drop for TokenCountingStream<S> {
fn drop(&mut self) { fn drop(&mut self) {
let state = self.state.clone(); let state = self.state.clone();
let is_llm_billing_enabled = state.config.is_llm_billing_enabled();
let claims = self.claims.clone(); let claims = self.claims.clone();
let provider = self.provider; let provider = self.provider;
let model = std::mem::take(&mut self.model); let model = std::mem::take(&mut self.model);
@@ -638,7 +641,14 @@ impl<S> Drop for TokenCountingStream<S> {
provider, provider,
&model, &model,
tokens, 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), Cents(claims.max_monthly_spend_in_cents),
Utc::now(), Utc::now(),
) )

View File

@@ -2237,7 +2237,7 @@ fn join_project_internal(
worktree_id: worktree.id, worktree_id: worktree.id,
path: settings_file.path, path: settings_file.path,
content: Some(settings_file.content), content: Some(settings_file.content),
kind: Some(settings_file.kind.to_proto() as i32), kind: Some(proto::update_user_settings::Kind::Settings.into()),
}, },
)?; )?;
} }

View File

@@ -12,7 +12,6 @@ use editor::{
test::editor_test_context::{AssertionContextManager, EditorTestContext}, test::editor_test_context::{AssertionContextManager, EditorTestContext},
Editor, Editor,
}; };
use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext}; use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use indoc::indoc; use indoc::indoc;
@@ -31,7 +30,7 @@ use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use std::{ use std::{
ops::Range, ops::Range,
path::{Path, PathBuf}, path::Path,
sync::{ sync::{
atomic::{self, AtomicBool, AtomicUsize}, atomic::{self, AtomicBool, AtomicUsize},
Arc, Arc,
@@ -61,7 +60,7 @@ async fn test_host_disconnect(
.fs() .fs()
.insert_tree( .insert_tree(
"/a", "/a",
json!({ serde_json::json!({
"a.txt": "a-contents", "a.txt": "a-contents",
"b.txt": "b-contents", "b.txt": "b-contents",
}), }),
@@ -2153,295 +2152,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
}); });
} }
#[gpui::test(iterations = 30)]
async fn test_collaborating_with_editorconfig(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(cx_a.executor()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_b.update(editor::init);
// Set up a fake language server.
client_a.language_registry().add(rust_lang());
client_a
.fs()
.insert_tree(
"/a",
json!({
"src": {
"main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
"other_mod": {
"other.rs": "pub fn foo() -> usize {\n 4\n}",
".editorconfig": "",
},
},
".editorconfig": "[*]\ntab_width = 2\n",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let main_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
// Join the project as client B.
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let main_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let initial_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
let initial_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
let first_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
false,
);
let first_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
false,
);
client_a
.fs()
.atomic_write(
PathBuf::from("/a/src/.editorconfig"),
"[*]\ntab_width = 3\n".to_owned(),
)
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
let second_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let second_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
false,
);
let editorconfig_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
})
.await
.unwrap();
editorconfig_buffer_b.update(cx_b, |buffer, cx| {
buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
});
project_b
.update(cx_b, |project, cx| {
project.save_buffer(editorconfig_buffer_b.clone(), cx)
})
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let third_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
false,
);
}
#[track_caller]
fn tab_undo_assert(
cx_a: &mut EditorTestContext,
cx_b: &mut EditorTestContext,
expected_initial: &str,
expected_tabbed: &str,
a_tabs: bool,
) {
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_tabbed);
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
}
fn extract_hint_labels(editor: &Editor) -> Vec<String> { fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new(); let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() { for hint in editor.inlay_hint_cache().hints() {

View File

@@ -34,7 +34,7 @@ use project::{
}; };
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::{LocalSettingsKind, SettingsStore};
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
env, future, mem, env, future, mem,
@@ -3328,8 +3328,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()), (
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), Path::new("").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":2}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
] ]
) )
}); });
@@ -3347,8 +3355,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("").into(), r#"{}"#.to_string()), (
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), Path::new("").into(),
LocalSettingsKind::Settings,
r#"{}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
] ]
) )
}); });
@@ -3376,8 +3392,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), (
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()), Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(
Path::new("b").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":4}"#.to_string()
),
] ]
) )
}); });
@@ -3407,7 +3431,11 @@ async fn test_local_settings(
store store
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),] &[(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"hard_tabs":true}"#.to_string()
),]
) )
}); });
} }

View File

@@ -3,7 +3,7 @@ use call::ActiveCall;
use fs::{FakeFs, Fs as _}; use fs::{FakeFs, Fs as _};
use gpui::{Context as _, TestAppContext}; use gpui::{Context as _, TestAppContext};
use http_client::BlockedHttpClient; use http_client::BlockedHttpClient;
use language::{language_settings::language_settings, LanguageRegistry}; use language::{language_settings::all_language_settings, LanguageRegistry};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use project::ProjectPath; use project::ProjectPath;
use remote::SshRemoteClient; use remote::SshRemoteClient;
@@ -26,7 +26,7 @@ async fn test_sharing_an_ssh_remote_project(
.await; .await;
// Set up project on remote FS // Set up project on remote FS
let (port, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx); let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
let remote_fs = FakeFs::new(server_cx.executor()); let remote_fs = FakeFs::new(server_cx.executor());
remote_fs remote_fs
.insert_tree( .insert_tree(
@@ -67,7 +67,6 @@ async fn test_sharing_an_ssh_remote_project(
) )
}); });
let client_ssh = SshRemoteClient::fake_client(port, cx_a).await;
let (project_a, worktree_id) = client_a let (project_a, worktree_id) = client_a
.build_ssh_project("/code/project1", client_ssh, cx_a) .build_ssh_project("/code/project1", client_ssh, cx_a)
.await; .await;
@@ -135,7 +134,9 @@ async fn test_sharing_an_ssh_remote_project(
cx_b.read(|cx| { cx_b.read(|cx| {
let file = buffer_b.read(cx).file(); let file = buffer_b.read(cx).file();
assert_eq!( assert_eq!(
language_settings(Some("Rust".into()), file, cx).language_servers, all_language_settings(file, cx)
.language(Some(&("Rust".into())))
.language_servers,
["override-rust-analyzer".to_string()] ["override-rust-analyzer".to_string()]
) )
}); });

View File

@@ -864,11 +864,7 @@ impl Copilot {
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let uri = registered_buffer.uri.clone(); let uri = registered_buffer.uri.clone();
let position = position.to_point_utf16(buffer); let position = position.to_point_utf16(buffer);
let settings = language_settings( let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
buffer.language_at(position).map(|l| l.name()),
buffer.file(),
cx,
);
let tab_size = settings.tab_size; let tab_size = settings.tab_size;
let hard_tabs = settings.hard_tabs; let hard_tabs = settings.hard_tabs;
let relative_path = buffer let relative_path = buffer

View File

@@ -77,7 +77,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
let file = buffer.file(); let file = buffer.file();
let language = buffer.language_at(cursor_position); let language = buffer.language_at(cursor_position);
let settings = all_language_settings(file, cx); let settings = all_language_settings(file, cx);
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx) settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
} }
fn refresh( fn refresh(
@@ -209,7 +209,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
) { ) {
let settings = AllLanguageSettings::get_global(cx); let settings = AllLanguageSettings::get_global(cx);
let copilot_enabled = settings.inline_completions_enabled(None, None, cx); let copilot_enabled = settings.inline_completions_enabled(None, None);
if !copilot_enabled { if !copilot_enabled {
return; return;

View File

@@ -962,6 +962,7 @@ fn random_diagnostic(
const FILE_HEADER: &str = "file header"; const FILE_HEADER: &str = "file header";
const EXCERPT_HEADER: &str = "excerpt header"; const EXCERPT_HEADER: &str = "excerpt header";
const EXCERPT_FOOTER: &str = "excerpt footer";
fn editor_blocks( fn editor_blocks(
editor: &View<Editor>, editor: &View<Editor>,
@@ -997,7 +998,7 @@ fn editor_blocks(
.ok()? .ok()?
} }
Block::ExcerptBoundary { Block::ExcerptHeader {
starts_new_buffer, .. starts_new_buffer, ..
} => { } => {
if *starts_new_buffer { if *starts_new_buffer {
@@ -1006,6 +1007,7 @@ fn editor_blocks(
EXCERPT_HEADER.into() EXCERPT_HEADER.into()
} }
} }
Block::ExcerptFooter { .. } => EXCERPT_FOOTER.into(),
}; };
Some((row, name)) Some((row, name))

View File

@@ -423,12 +423,11 @@ impl DisplayMap {
} }
fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 { fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer let language = buffer
.and_then(|buffer| buffer.language()) .read(cx)
.map(|l| l.name()); .as_singleton()
let file = buffer.and_then(|buffer| buffer.file()); .and_then(|buffer| buffer.read(cx).language());
language_settings(language, file, cx).tab_size language_settings(language, None, cx).tab_size
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -5,8 +5,8 @@ use super::{
use crate::{EditorStyle, GutterDimensions}; use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet}; use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, EntityId, Pixels, WindowContext}; use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point}; use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _}; use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
cell::RefCell, cell::RefCell,
@@ -128,17 +128,26 @@ pub struct BlockContext<'a, 'b> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockId { pub enum BlockId {
Custom(CustomBlockId), 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 { impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self { fn from(value: BlockId) -> Self {
match value { match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(), BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt { BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(), BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
None => "LastExcerptBoundary".into(),
},
} }
} }
} }
@@ -147,7 +156,8 @@ impl std::fmt::Display for BlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Custom(id) => write!(f, "Block({id:?})"), 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 { pub(crate) enum BlockType {
Custom(CustomBlockId), Custom(CustomBlockId),
ExcerptBoundary, Header,
Footer,
} }
pub(crate) trait BlockLike { pub(crate) trait BlockLike {
@@ -180,20 +191,27 @@ pub(crate) trait BlockLike {
#[derive(Clone)] #[derive(Clone)]
pub enum Block { pub enum Block {
Custom(Arc<CustomBlock>), Custom(Arc<CustomBlock>),
ExcerptBoundary { ExcerptHeader {
prev_excerpt: Option<ExcerptInfo>, id: ExcerptId,
next_excerpt: Option<ExcerptInfo>, buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>,
height: u32, height: u32,
starts_new_buffer: bool, starts_new_buffer: bool,
show_excerpt_controls: bool, show_excerpt_controls: bool,
}, },
ExcerptFooter {
id: ExcerptId,
disposition: BlockDisposition,
height: u32,
},
} }
impl BlockLike for Block { impl BlockLike for Block {
fn block_type(&self) -> BlockType { fn block_type(&self) -> BlockType {
match self { match self {
Block::Custom(block) => BlockType::Custom(block.id), 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 { fn priority(&self) -> usize {
match self { match self {
Block::Custom(block) => block.priority, 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 { pub fn id(&self) -> BlockId {
match self { match self {
Block::Custom(block) => BlockId::Custom(block.id), Block::Custom(block) => BlockId::Custom(block.id),
Block::ExcerptBoundary { next_excerpt, .. } => { Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id)) Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
}
} }
} }
fn disposition(&self) -> BlockDisposition { fn disposition(&self) -> BlockDisposition {
match self { match self {
Block::Custom(block) => block.disposition, Block::Custom(block) => block.disposition,
Block::ExcerptBoundary { next_excerpt, .. } => { Block::ExcerptHeader { .. } => BlockDisposition::Above,
if next_excerpt.is_some() { Block::ExcerptFooter { disposition, .. } => *disposition,
BlockDisposition::Above
} else {
BlockDisposition::Below
}
}
} }
} }
pub fn height(&self) -> u32 { pub fn height(&self) -> u32 {
match self { match self {
Block::Custom(block) => block.height, Block::Custom(block) => block.height,
Block::ExcerptBoundary { height, .. } => *height, Block::ExcerptHeader { height, .. } => *height,
Block::ExcerptFooter { height, .. } => *height,
} }
} }
pub fn style(&self) -> BlockStyle { pub fn style(&self) -> BlockStyle {
match self { match self {
Block::Custom(block) => block.style, 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(), Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
Self::ExcerptBoundary { Self::ExcerptHeader {
buffer,
starts_new_buffer, starts_new_buffer,
next_excerpt, id,
prev_excerpt,
.. ..
} => f } => f
.debug_struct("ExcerptBoundary") .debug_struct("ExcerptHeader")
.field("prev_excerpt", &prev_excerpt) .field("id", &id)
.field("next_excerpt", &next_excerpt) .field("path", &buffer.file().map(|f| f.path()))
.field("starts_new_buffer", &starts_new_buffer) .field("starts_new_buffer", &starts_new_buffer)
.finish(), .finish(),
Block::ExcerptFooter {
id, disposition, ..
} => f
.debug_struct("ExcerptFooter")
.field("id", &id)
.field("disposition", &disposition)
.finish(),
} }
} }
} }
@@ -573,62 +595,66 @@ impl BlockMap {
{ {
buffer buffer
.excerpt_boundaries_in_range(range) .excerpt_boundaries_in_range(range)
.filter_map(move |excerpt_boundary| { .flat_map(move |excerpt_boundary| {
let wrap_row; let mut wrap_row = wrap_snapshot
if excerpt_boundary.next.is_some() { .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
wrap_row = wrap_snapshot .row();
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
.row(); [
} else { show_excerpt_controls
wrap_row = wrap_snapshot .then(|| {
.make_wrap_point( let disposition;
Point::new( if excerpt_boundary.next.is_some() {
excerpt_boundary.row.0, disposition = BlockDisposition::Above;
buffer.line_len(excerpt_boundary.row), } else {
), wrap_row = wrap_snapshot
Bias::Left, .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)]) { pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut [(u32, B)]) {
@@ -639,9 +665,12 @@ impl BlockMap {
.disposition() .disposition()
.cmp(&block_b.disposition()) .cmp(&block_b.disposition())
.then_with(|| match ((block_a.block_type()), (block_b.block_type())) { .then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
(BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal, (BlockType::Footer, BlockType::Footer) => Ordering::Equal,
(BlockType::ExcerptBoundary, _) => Ordering::Less, (BlockType::Footer, _) => Ordering::Less,
(_, BlockType::ExcerptBoundary) => Ordering::Greater, (_, 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 (BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
.priority() .priority()
.cmp(&block_a.priority()) .cmp(&block_a.priority())
@@ -1016,19 +1045,33 @@ impl BlockSnapshot {
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?; let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
Some(Block::Custom(custom_block.clone())) Some(Block::Custom(custom_block.clone()))
} }
BlockId::ExcerptBoundary(next_excerpt_id) => { BlockId::ExcerptHeader(excerpt_id) => {
let wrap_point; let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
if let Some(next_excerpt_id) = next_excerpt_id { let wrap_point = self
let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?; .wrap_snapshot
wrap_point = self .make_wrap_point(excerpt_range.start, Bias::Left);
.wrap_snapshot let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
.make_wrap_point(excerpt_range.start, Bias::Left); cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
} else { while let Some(transform) = cursor.item() {
wrap_point = self if let Some(block) = transform.block.as_ref() {
.wrap_snapshot if block.id() == block_id {
.make_wrap_point(buffer.max_point(), Bias::Left); 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)>(&()); let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() { while let Some(transform) = cursor.item() {
@@ -1425,7 +1468,7 @@ mod tests {
}; };
use gpui::{div, font, px, AppContext, Context as _, Element}; use gpui::{div, font, px, AppContext, Context as _, Element};
use language::{Buffer, Capability}; use language::{Buffer, Capability};
use multi_buffer::{ExcerptRange, MultiBuffer}; use multi_buffer::MultiBuffer;
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
use std::env; 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. // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
assert_eq!( assert_eq!(
snapshot.text(), 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 let blocks: Vec<_> = snapshot
.blocks_in_range(0..u32::MAX) .blocks_in_range(0..u32::MAX)
.map(|(row, block)| (row..row + block.height(), block.id())) .map(|(row, block)| (row, block.id()))
.collect(); .collect();
assert_eq!( assert_eq!(
blocks, blocks,
vec![ vec![
(0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header (0, BlockId::ExcerptHeader(excerpt_ids[0])),
(4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header (3, BlockId::ExcerptFooter(excerpt_ids[0])),
(9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header (4, BlockId::ExcerptHeader(excerpt_ids[1])),
(14..15, BlockId::ExcerptBoundary(None)), // footer (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)] #[derive(Debug, Eq, PartialEq)]
enum ExpectedBlock { enum ExpectedBlock {
ExcerptBoundary { ExcerptHeader {
height: u32, height: u32,
starts_new_buffer: bool, starts_new_buffer: bool,
is_last: bool, },
ExcerptFooter {
height: u32,
disposition: BlockDisposition,
}, },
Custom { Custom {
disposition: BlockDisposition, disposition: BlockDisposition,
@@ -2255,7 +2303,8 @@ mod tests {
fn block_type(&self) -> BlockType { fn block_type(&self) -> BlockType {
match self { match self {
ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id), 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 { fn priority(&self) -> usize {
match self { match self {
ExpectedBlock::Custom { priority, .. } => *priority, ExpectedBlock::Custom { priority, .. } => *priority,
ExpectedBlock::ExcerptBoundary { .. } => usize::MAX, ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
ExpectedBlock::ExcerptFooter { .. } => 0,
} }
} }
} }
@@ -2274,21 +2324,17 @@ mod tests {
impl ExpectedBlock { impl ExpectedBlock {
fn height(&self) -> u32 { fn height(&self) -> u32 {
match self { match self {
ExpectedBlock::ExcerptBoundary { height, .. } => *height, ExpectedBlock::ExcerptHeader { height, .. } => *height,
ExpectedBlock::Custom { height, .. } => *height, ExpectedBlock::Custom { height, .. } => *height,
ExpectedBlock::ExcerptFooter { height, .. } => *height,
} }
} }
fn disposition(&self) -> BlockDisposition { fn disposition(&self) -> BlockDisposition {
match self { match self {
ExpectedBlock::ExcerptBoundary { is_last, .. } => { ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
if *is_last {
BlockDisposition::Below
} else {
BlockDisposition::Above
}
}
ExpectedBlock::Custom { disposition, .. } => *disposition, ExpectedBlock::Custom { disposition, .. } => *disposition,
ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
} }
} }
} }
@@ -2302,15 +2348,21 @@ mod tests {
height: block.height, height: block.height,
priority: block.priority, priority: block.priority,
}, },
Block::ExcerptBoundary { Block::ExcerptHeader {
height, height,
starts_new_buffer, starts_new_buffer,
next_excerpt,
.. ..
} => ExpectedBlock::ExcerptBoundary { } => ExpectedBlock::ExcerptHeader {
height, height,
starts_new_buffer, 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> { fn as_custom(&self) -> Option<&CustomBlock> {
match self { match self {
Block::Custom(block) => Some(block), Block::Custom(block) => Some(block),
Block::ExcerptBoundary { .. } => None, Block::ExcerptHeader { .. } => None,
Block::ExcerptFooter { .. } => None,
} }
} }
} }

View File

@@ -73,12 +73,12 @@ use git::blame::GitBlame;
use gpui::{ use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement, div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UTF16Selection, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
WeakFocusHandle, WeakView, WindowContext, VisualContext, WeakFocusHandle, WeakView, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@@ -90,7 +90,7 @@ pub use inline_completion_provider::*;
pub use items::MAX_TAB_TITLE_LEN; pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId, Point, Selection, SelectionGoal, TransactionId,
@@ -171,7 +171,7 @@ use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSetti
use crate::hover_links::find_url; use crate::hover_links::find_url;
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState}; 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_HEADER_HEIGHT: u32 = 1;
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1; pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2; pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
@@ -428,7 +428,8 @@ impl Default for EditorStyle {
} }
pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle { pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
let show_background = language_settings::language_settings(None, None, cx) let show_background = all_language_settings(None, cx)
.language(None)
.inlay_hints .inlay_hints
.show_background; .show_background;
@@ -639,6 +640,7 @@ pub struct Editor {
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>, tasks_update_task: Option<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>, previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
file_header_size: u32,
breadcrumb_header: Option<String>, breadcrumb_header: Option<String>,
focused_block: Option<FocusedBlock>, focused_block: Option<FocusedBlock>,
next_scroll_position: NextScrollCursorCenterTopBottom, next_scroll_position: NextScrollCursorCenterTopBottom,
@@ -1844,6 +1846,7 @@ impl Editor {
}), }),
merge_adjacent: true, merge_adjacent: true,
}; };
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
let display_map = cx.new_model(|cx| { let display_map = cx.new_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
@@ -1851,7 +1854,7 @@ impl Editor {
font_size, font_size,
None, None,
show_excerpt_controls, show_excerpt_controls,
FILE_HEADER_HEIGHT, file_header_size,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT, MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
fold_placeholder, fold_placeholder,
@@ -2035,6 +2038,7 @@ impl Editor {
.restore_unsaved_buffers, .restore_unsaved_buffers,
blame: None, blame: None,
blame_subscription: None, blame_subscription: None,
file_header_size,
tasks: Default::default(), tasks: Default::default(),
_subscriptions: vec![ _subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed), cx.observe(&buffer, Self::on_buffer_changed),
@@ -4247,10 +4251,7 @@ impl Editor {
.text_anchor_for_position(position, cx)?; .text_anchor_for_position(position, cx)?;
let settings = language_settings::language_settings( let settings = language_settings::language_settings(
buffer buffer.read(cx).language_at(buffer_position).as_ref(),
.read(cx)
.language_at(buffer_position)
.map(|l| l.name()),
buffer.read(cx).file(), buffer.read(cx).file(),
cx, cx,
); );
@@ -6284,9 +6285,11 @@ impl Editor {
let project_path = buffer.read(cx).project_path(cx)?; let project_path = buffer.read(cx).project_path(cx)?;
let project = self.project.as_ref()?.read(cx); let project = self.project.as_ref()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?; let entry = project.entry_for_path(&project_path, cx)?;
let parent = match &entry.canonical_path { let abs_path = project.absolute_path(&project_path, cx)?;
Some(canonical_path) => canonical_path.to_path_buf(), let parent = if entry.is_symlink {
None => project.absolute_path(&project_path, cx)?, abs_path.canonicalize().ok()?
} else {
abs_path
} }
.parent()? .parent()?
.to_path_buf(); .to_path_buf();
@@ -12805,7 +12808,7 @@ impl Editor {
} }
pub fn file_header_size(&self) -> u32 { pub fn file_header_size(&self) -> u32 {
FILE_HEADER_HEIGHT self.file_header_size
} }
pub fn revert( pub fn revert(
@@ -13376,8 +13379,11 @@ fn inlay_hint_settings(
cx: &mut ViewContext<'_, Editor>, cx: &mut ViewContext<'_, Editor>,
) -> InlayHintSettings { ) -> InlayHintSettings {
let file = snapshot.file_at(location); let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name()); let language = snapshot.language_at(location);
language_settings(language, file, cx).inlay_hints let settings = all_language_settings(file, cx);
settings
.language(language.map(|l| l.name()).as_ref())
.inlay_hints
} }
fn consume_contiguous_rows( fn consume_contiguous_rows(
@@ -14114,7 +14120,7 @@ pub fn diagnostic_block_renderer(
let multi_line_diagnostic = diagnostic.message.contains('\n'); let multi_line_diagnostic = diagnostic.message.contains('\n');
let buttons = |diagnostic: &Diagnostic| { let buttons = |diagnostic: &Diagnostic, block_id: BlockId| {
if multi_line_diagnostic { if multi_line_diagnostic {
v_flex() v_flex()
} else { } else {
@@ -14122,7 +14128,7 @@ pub fn diagnostic_block_renderer(
} }
.when(allow_closing, |div| { .when(allow_closing, |div| {
div.children(diagnostic.is_primary.then(|| { 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) .icon_color(Color::Muted)
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
@@ -14132,7 +14138,7 @@ pub fn diagnostic_block_renderer(
})) }))
}) })
.child( .child(
IconButton::new("copy-block", IconName::Copy) IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
@@ -14147,7 +14153,7 @@ pub fn diagnostic_block_renderer(
) )
}; };
let icon_size = buttons(&diagnostic) let icon_size = buttons(&diagnostic, cx.block_id)
.into_any_element() .into_any_element()
.layout_as_root(AvailableSpace::min_size(), cx); .layout_as_root(AvailableSpace::min_size(), cx);
@@ -14164,7 +14170,7 @@ pub fn diagnostic_block_renderer(
.w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width) .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
.flex_shrink(), .flex_shrink(),
) )
.child(buttons(&diagnostic)) .child(buttons(&diagnostic, cx.block_id))
.child(div().flex().flex_shrink_0().child( .child(div().flex().flex_shrink_0().child(
StyledText::new(text_without_backticks.clone()).with_highlights( StyledText::new(text_without_backticks.clone()).with_highlights(
&text_style, &text_style,

View File

@@ -21,8 +21,7 @@ use crate::{
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
}; };
use client::ParticipantIndex; use client::ParticipantIndex;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
@@ -32,7 +31,7 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg, anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, 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, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
@@ -47,7 +46,7 @@ use language::{
ChunkRendererContext, ChunkRendererContext,
}; };
use lsp::DiagnosticSeverity; use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, ExcerptId, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow}; use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
use project::{ use project::{
project_settings::{GitGutterSetting, ProjectSettings}, project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath, ProjectPath,
@@ -1633,7 +1632,7 @@ impl EditorElement {
let mut block_offset = 0; let mut block_offset = 0;
let mut found_excerpt_header = false; let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) { 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; found_excerpt_header = true;
break; break;
} }
@@ -1650,7 +1649,7 @@ impl EditorElement {
let mut block_height = 0; let mut block_height = 0;
let mut found_excerpt_header = false; let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) { 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; found_excerpt_header = true;
} }
block_height += block.height(); block_height += block.height();
@@ -2101,14 +2100,23 @@ impl EditorElement {
.into_any_element() .into_any_element()
} }
Block::ExcerptBoundary { Block::ExcerptHeader {
prev_excerpt, buffer,
next_excerpt, range,
show_excerpt_controls,
starts_new_buffer, starts_new_buffer,
height, 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)] #[derive(Clone)]
struct JumpData { struct JumpData {
position: Point, position: Point,
@@ -2117,227 +2125,233 @@ impl EditorElement {
line_offset_from_top: u32, 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 let icon_offset = gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin); - (gutter_dimensions.left_padding + gutter_dimensions.margin);
let header_padding = px(6.0); let element = if *starts_new_buffer {
let path = buffer.resolve_file_path(cx, include_root);
let mut result = v_flex().id(block_id).w_full(); let mut filename = None;
let mut parent_path = None;
if let Some(prev_excerpt) = prev_excerpt { // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if *show_excerpt_controls { if let Some(path) = path {
result = result.child( filename = path.file_name().map(|f| f.to_string_lossy().to_string());
h_flex() parent_path = path
.w(icon_offset) .parent()
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height()) .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
.flex_none()
.justify_end()
.child(self.render_expand_excerpt_button(
prev_excerpt.id,
ExpandExcerptDirection::Down,
IconName::ArrowDownFromLine,
cx,
)),
);
} }
}
if let Some(next_excerpt) = next_excerpt { let header_padding = px(6.0);
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 excerpt_start = range.context.start; v_flex()
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); .id(("path excerpt header", EntityId::from(block_id)))
let offset_from_excerpt_start = if jump_anchor == excerpt_start { .w_full()
0 .px(header_padding)
} else { .pt(header_padding)
let excerpt_start_row = .child(
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(
h_flex() h_flex()
.id("excerpt header block") .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.group("excerpt-jump-action") .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() .justify_start()
.w_full() .id("jump to collapsed context")
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height()) .w(relative(1.0))
.relative() .h_full()
.child( .child(
div() div()
.top(px(0.))
.absolute()
.w_full()
.h_px() .h_px()
.w_full()
.bg(cx.theme().colors().border_variant) .bg(cx.theme().colors().border_variant)
.group_hover("excerpt-jump-action", |style| { .group_hover("excerpt-jump-action", |style| {
style.bg(cx.theme().colors().border) style.bg(cx.theme().colors().border)
}), }),
) ),
.cursor_pointer() )
.when_some(jump_data.clone(), |this, jump_data| { .child(
this.on_click(cx.listener_for(&self.editor, { h_flex()
let path = jump_data.path.clone(); .justify_end()
move |editor, _, cx| { .flex_none()
cx.stop_propagation(); .w(icon_offset)
.h_full()
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( .child(
h_flex() show_excerpt_controls
.w(icon_offset) .then(|| {
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 ButtonLike::new("expand-icon")
* cx.line_height()) .style(ButtonStyle::Transparent)
.flex_none() .child(
.justify_end() svg()
.child(if *show_excerpt_controls { .path(IconName::ArrowUpFromLine.path())
self.render_expand_excerpt_button( .size(IconSize::XSmall.rems())
next_excerpt.id, .text_color(
ExpandExcerptDirection::Up, cx.theme().colors().editor_line_number,
IconName::ArrowUpFromLine, )
cx, .group("")
) .hover(|style| {
} else { 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") ButtonLike::new("jump-icon")
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.child( .child(
@@ -2347,6 +2361,7 @@ impl EditorElement {
.text_color( .text_color(
cx.theme().colors().border_variant, cx.theme().colors().border_variant,
) )
.group("excerpt-jump-action")
.group_hover( .group_hover(
"excerpt-jump-action", "excerpt-jump-action",
|style| { |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) (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)] #[allow(clippy::too_many_arguments)]
fn render_blocks( fn render_blocks(
&self, &self,
@@ -3274,7 +3367,7 @@ impl EditorElement {
let end_row_in_current_excerpt = snapshot let end_row_in_current_excerpt = snapshot
.blocks_in_range(start_row..end_row) .blocks_in_range(start_row..end_row)
.find_map(|(start_row, block)| { .find_map(|(start_row, block)| {
if matches!(block, Block::ExcerptBoundary { .. }) { if matches!(block, Block::ExcerptHeader { .. }) {
Some(start_row) Some(start_row)
} else { } else {
None None

View File

@@ -39,13 +39,9 @@ impl Editor {
) -> Option<Vec<MultiBufferIndentGuide>> { ) -> Option<Vec<MultiBufferIndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| { let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() { if let Some(buffer) = self.buffer().read(cx).as_singleton() {
language_settings( language_settings(buffer.read(cx).language(), buffer.read(cx).file(), cx)
buffer.read(cx).language().map(|l| l.name()), .indent_guides
buffer.read(cx).file(), .enabled
cx,
)
.indent_guides
.enabled
} else { } else {
true true
} }

View File

@@ -952,7 +952,7 @@ mod tests {
px(14.0), px(14.0),
None, None,
true, true,
0, 2,
2, 2,
0, 0,
FoldPlaceholder::test(), FoldPlaceholder::test(),

View File

@@ -356,11 +356,8 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() { cx.update(|cx| match category.as_str() {
"language" => { "language" => {
let key = key.map(|k| LanguageName::new(&k)); let key = key.map(|k| LanguageName::new(&k));
let settings = AllLanguageSettings::get(location, cx).language( let settings =
location, AllLanguageSettings::get(location, cx).language(key.as_ref());
key.as_ref(),
cx,
);
Ok(serde_json::to_string(&settings::LanguageSettings { Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size, tab_size: settings.tab_size,
})?) })?)

View File

@@ -402,11 +402,8 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() { cx.update(|cx| match category.as_str() {
"language" => { "language" => {
let key = key.map(|k| LanguageName::new(&k)); let key = key.map(|k| LanguageName::new(&k));
let settings = AllLanguageSettings::get(location, cx).language( let settings =
location, AllLanguageSettings::get(location, cx).language(key.as_ref());
key.as_ref(),
cx,
);
Ok(serde_json::to_string(&settings::LanguageSettings { Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size, tab_size: settings.tab_size,
})?) })?)

View File

@@ -62,7 +62,7 @@ impl Render for InlineCompletionButton {
let status = copilot.read(cx).status(); let status = copilot.read(cx).status();
let enabled = self.editor_enabled.unwrap_or_else(|| { let enabled = self.editor_enabled.unwrap_or_else(|| {
all_language_settings.inline_completions_enabled(None, None, cx) all_language_settings.inline_completions_enabled(None, None)
}); });
let icon = match status { let icon = match status {
@@ -248,9 +248,8 @@ impl InlineCompletionButton {
if let Some(language) = self.language.clone() { if let Some(language) = self.language.clone() {
let fs = fs.clone(); let fs = fs.clone();
let language_enabled = let language_enabled = language_settings::language_settings(Some(&language), None, cx)
language_settings::language_settings(Some(language.name()), None, cx) .show_inline_completions;
.show_inline_completions;
menu = menu.entry( menu = menu.entry(
format!( format!(
@@ -293,7 +292,7 @@ impl InlineCompletionButton {
); );
} }
let globally_enabled = settings.inline_completions_enabled(None, None, cx); let globally_enabled = settings.inline_completions_enabled(None, None);
menu.entry( menu.entry(
if globally_enabled { if globally_enabled {
"Hide Inline Completions for All Files" "Hide Inline Completions for All Files"
@@ -341,7 +340,6 @@ impl InlineCompletionButton {
&& all_language_settings(file, cx).inline_completions_enabled( && all_language_settings(file, cx).inline_completions_enabled(
language, language,
file.map(|file| file.path().as_ref()), file.map(|file| file.path().as_ref()),
cx,
), ),
) )
}; };
@@ -444,7 +442,7 @@ async fn configure_disabled_globs(
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) { fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_inline_completions = let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(None, None, cx); all_language_settings(None, cx).inline_completions_enabled(None, None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| { update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_inline_completions = Some(!show_inline_completions) file.defaults.show_inline_completions = Some(!show_inline_completions)
}); });
@@ -468,7 +466,7 @@ fn toggle_inline_completions_for_language(
cx: &mut AppContext, cx: &mut AppContext,
) { ) {
let show_inline_completions = let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(Some(&language), None, cx); all_language_settings(None, cx).inline_completions_enabled(Some(&language), None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| { update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages file.languages
.entry(language.name()) .entry(language.name())

View File

@@ -30,7 +30,6 @@ async-trait.workspace = true
async-watch.workspace = true async-watch.workspace = true
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
ec4rs.workspace = true
futures.workspace = true futures.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
git.workspace = true git.workspace = true

View File

@@ -37,7 +37,6 @@ use smallvec::SmallVec;
use smol::future::yield_now; use smol::future::yield_now;
use std::{ use std::{
any::Any, any::Any,
borrow::Cow,
cell::Cell, cell::Cell,
cmp::{self, Ordering, Reverse}, cmp::{self, Ordering, Reverse},
collections::BTreeMap, collections::BTreeMap,
@@ -2491,11 +2490,7 @@ impl BufferSnapshot {
/// Returns [`IndentSize`] for a given position that respects user settings /// Returns [`IndentSize`] for a given position that respects user settings
/// and language preferences. /// and language preferences.
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize { pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
let settings = language_settings( let settings = language_settings(self.language_at(position), self.file(), cx);
self.language_at(position).map(|l| l.name()),
self.file(),
cx,
);
if settings.hard_tabs { if settings.hard_tabs {
IndentSize::tab() IndentSize::tab()
} else { } else {
@@ -2828,15 +2823,11 @@ impl BufferSnapshot {
/// Returns the settings for the language at the given location. /// Returns the settings for the language at the given location.
pub fn settings_at<'a, D: ToOffset>( pub fn settings_at<'a, D: ToOffset>(
&'a self, &self,
position: D, position: D,
cx: &'a AppContext, cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> { ) -> &'a LanguageSettings {
language_settings( language_settings(self.language_at(position), self.file.as_ref(), cx)
self.language_at(position).map(|l| l.name()),
self.file.as_ref(),
cx,
)
} }
pub fn char_classifier_at<T: ToOffset>(&self, point: T) -> CharClassifier { pub fn char_classifier_at<T: ToOffset>(&self, point: T) -> CharClassifier {
@@ -3538,8 +3529,7 @@ impl BufferSnapshot {
ignore_disabled_for_language: bool, ignore_disabled_for_language: bool,
cx: &AppContext, cx: &AppContext,
) -> Vec<IndentGuide> { ) -> Vec<IndentGuide> {
let language_settings = let language_settings = language_settings(self.language(), self.file.as_ref(), cx);
language_settings(self.language().map(|l| l.name()), self.file.as_ref(), cx);
let settings = language_settings.indent_guides; let settings = language_settings.indent_guides;
if !ignore_disabled_for_language && !settings.enabled { if !ignore_disabled_for_language && !settings.enabled {
return Vec::new(); return Vec::new();

View File

@@ -15,6 +15,7 @@ mod outline;
pub mod proto; pub mod proto;
mod syntax_map; mod syntax_map;
mod task_context; mod task_context;
mod toolchain;
#[cfg(test)] #[cfg(test)]
pub mod buffer_tests; pub mod buffer_tests;
@@ -61,6 +62,7 @@ use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag; use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange}; pub use task_context::{ContextProvider, RunnableRange};
use theme::SyntaxTheme; use theme::SyntaxTheme;
pub use toolchain::{Toolchain, ToolchainList, ToolchainLister};
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore}; use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
use util::serde::default_true; use util::serde::default_true;

View File

@@ -4,10 +4,6 @@ use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use core::slice; use core::slice;
use ec4rs::{
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
Properties as EditorconfigProperties,
};
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::AppContext; use gpui::AppContext;
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
@@ -20,10 +16,8 @@ use serde::{
Deserialize, Deserializer, Serialize, Deserialize, Deserializer, Serialize,
}; };
use serde_json::Value; use serde_json::Value;
use settings::{ use settings::{add_references_to_properties, Settings, SettingsLocation, SettingsSources};
add_references_to_properties, Settings, SettingsLocation, SettingsSources, SettingsStore, use std::{num::NonZeroU32, path::Path, sync::Arc};
};
use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
use util::serde::default_true; use util::serde::default_true;
/// Initializes the language settings. /// Initializes the language settings.
@@ -33,20 +27,17 @@ pub fn init(cx: &mut AppContext) {
/// Returns the settings for the specified language from the provided file. /// Returns the settings for the specified language from the provided file.
pub fn language_settings<'a>( pub fn language_settings<'a>(
language: Option<LanguageName>, language: Option<&Arc<Language>>,
file: Option<&'a Arc<dyn File>>, file: Option<&Arc<dyn File>>,
cx: &'a AppContext, cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> { ) -> &'a LanguageSettings {
let location = file.map(|f| SettingsLocation { let language_name = language.map(|l| l.name());
worktree_id: f.worktree_id(cx), all_language_settings(file, cx).language(language_name.as_ref())
path: f.path().as_ref(),
});
AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx)
} }
/// Returns the settings for all languages from the provided file. /// Returns the settings for all languages from the provided file.
pub fn all_language_settings<'a>( pub fn all_language_settings<'a>(
file: Option<&'a Arc<dyn File>>, file: Option<&Arc<dyn File>>,
cx: &'a AppContext, cx: &'a AppContext,
) -> &'a AllLanguageSettings { ) -> &'a AllLanguageSettings {
let location = file.map(|f| SettingsLocation { let location = file.map(|f| SettingsLocation {
@@ -819,27 +810,13 @@ impl InlayHintSettings {
impl AllLanguageSettings { impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name. /// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>( pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
&'a self, if let Some(name) = language_name {
location: Option<SettingsLocation<'a>>, if let Some(overrides) = self.languages.get(name) {
language_name: Option<&LanguageName>, return overrides;
cx: &'a AppContext, }
) -> Cow<'a, LanguageSettings> {
let settings = language_name
.and_then(|name| self.languages.get(name))
.unwrap_or(&self.defaults);
let editorconfig_properties = location.and_then(|location| {
cx.global::<SettingsStore>()
.editorconfg_properties(location.worktree_id, location.path)
});
if let Some(editorconfig_properties) = editorconfig_properties {
let mut settings = settings.clone();
merge_with_editorconfig(&mut settings, &editorconfig_properties);
Cow::Owned(settings)
} else {
Cow::Borrowed(settings)
} }
&self.defaults
} }
/// Returns whether inline completions are enabled for the given path. /// Returns whether inline completions are enabled for the given path.
@@ -856,7 +833,6 @@ impl AllLanguageSettings {
&self, &self,
language: Option<&Arc<Language>>, language: Option<&Arc<Language>>,
path: Option<&Path>, path: Option<&Path>,
cx: &AppContext,
) -> bool { ) -> bool {
if let Some(path) = path { if let Some(path) = path {
if !self.inline_completions_enabled_for_path(path) { if !self.inline_completions_enabled_for_path(path) {
@@ -864,64 +840,11 @@ impl AllLanguageSettings {
} }
} }
self.language(None, language.map(|l| l.name()).as_ref(), cx) self.language(language.map(|l| l.name()).as_ref())
.show_inline_completions .show_inline_completions
} }
} }
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
let max_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
MaxLineLen::Value(u) => Some(u as u32),
MaxLineLen::Off => None,
});
let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
IndentSize::Value(u) => NonZeroU32::new(u as u32),
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
TabWidth::Value(u) => NonZeroU32::new(u as u32),
}),
});
let hard_tabs = cfg
.get::<IndentStyle>()
.map(|v| v.eq(&IndentStyle::Tabs))
.ok();
let ensure_final_newline_on_save = cfg
.get::<FinalNewline>()
.map(|v| match v {
FinalNewline::Value(b) => b,
})
.ok();
let remove_trailing_whitespace_on_save = cfg
.get::<TrimTrailingWs>()
.map(|v| match v {
TrimTrailingWs::Value(b) => b,
})
.ok();
let preferred_line_length = max_line_length;
let soft_wrap = if max_line_length.is_some() {
Some(SoftWrap::PreferredLineLength)
} else {
None
};
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}
merge(&mut settings.tab_size, tab_size);
merge(&mut settings.hard_tabs, hard_tabs);
merge(
&mut settings.remove_trailing_whitespace_on_save,
remove_trailing_whitespace_on_save,
);
merge(
&mut settings.ensure_final_newline_on_save,
ensure_final_newline_on_save,
);
merge(&mut settings.preferred_line_length, preferred_line_length);
merge(&mut settings.soft_wrap, soft_wrap);
}
/// The kind of an inlay hint. /// The kind of an inlay hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlayHintKind { pub enum InlayHintKind {

View File

@@ -0,0 +1,32 @@
//! Provides support for language toolchains.
//!
//! A language can have associated toolchains,
//! which is a set of tools used to interact with the projects written in said language.
//! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
use async_trait::async_trait;
use gpui::SharedString;
/// Represents a single toolchain.
pub struct Toolchain {
/// User-facing label
pub label: SharedString,
}
#[async_trait]
pub trait ToolchainLister {
async fn list(&self) -> ToolchainList;
async fn activate(&self, _: Toolchain);
}
type DefaultIndex = usize;
pub struct ToolchainList {
toolchains: Vec<Toolchain>,
default: Option<DefaultIndex>,
}
impl ToolchainList {
pub fn toolchains(&self) -> &[Toolchain] {
&self.toolchains
}
}

View File

@@ -1,6 +1,6 @@
name = "CSS" name = "CSS"
grammar = "css" grammar = "css"
path_suffixes = ["css", "postcss"] path_suffixes = ["css"]
autoclose_before = ";:.,=}])>" autoclose_before = ";:.,=}])>"
brackets = [ brackets = [
{ start = "{", end = "}", close = true, newline = true }, { start = "{", end = "}", close = true, newline = true },

View File

@@ -405,6 +405,7 @@ fn adjust_runs(
runs runs
} }
#[derive(Default)]
pub(crate) struct GoContextProvider; pub(crate) struct GoContextProvider;
const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE")); const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));

View File

@@ -30,6 +30,121 @@ mod yaml;
#[exclude = "*.rs"] #[exclude = "*.rs"]
struct LanguageDir; struct LanguageDir;
mod dsl {
use std::sync::Arc;
use language::{ContextProvider, LanguageRegistry, LspAdapter};
use crate::{load_config, load_queries};
pub(super) struct LanguageBootstrapRecipe<'a> {
name: &'static str,
adapters: &'a [Arc<dyn LspAdapter>],
context_provider: Option<Box<dyn Fn() -> Arc<dyn ContextProvider> + 'static + Send + Sync>>,
}
impl From<&'static str> for LanguageBootstrapRecipe<'static> {
fn from(name: &'static str) -> Self {
Self {
name,
adapters: &[],
context_provider: None,
}
}
}
impl<'a, const ADAPTER_COUNT: usize>
From<(&'static str, &'a [Arc<dyn LspAdapter>; ADAPTER_COUNT])>
for LanguageBootstrapRecipe<'a>
{
fn from(value: (&'static str, &'a [Arc<dyn LspAdapter>; ADAPTER_COUNT])) -> Self {
Self {
name: value.0,
adapters: value.1.as_ref(),
context_provider: None,
}
}
}
impl<
'a,
const ADAPTER_COUNT: usize,
T: ContextProvider + 'static,
Callback: Fn() -> T + 'static + Send + Sync,
>
From<(
&'static str,
&'a [Arc<dyn LspAdapter>; ADAPTER_COUNT],
Callback,
)> for LanguageBootstrapRecipe<'a>
{
fn from(
value: (
&'static str,
&'a [Arc<dyn LspAdapter>; ADAPTER_COUNT],
Callback,
),
) -> Self {
Self {
name: value.0,
adapters: value.1.as_ref(),
context_provider: Some(Box::new(move || Arc::new((value.2)()))),
}
}
}
impl<T: ContextProvider + 'static, Callback: Fn() -> T + 'static + Send + Sync>
From<(&'static str, Callback)> for LanguageBootstrapRecipe<'static>
{
fn from(value: (&'static str, Callback)) -> Self {
Self {
name: value.0,
adapters: &[],
context_provider: Some(Box::new(move || Arc::new((value.1)()))),
}
}
}
pub(super) fn language<'a>(
languages: &LanguageRegistry,
config: impl Into<LanguageBootstrapRecipe<'a>>,
) {
let config = config.into();
language_impl(languages, config)
}
fn language_impl<'a>(
languages: &LanguageRegistry,
bootstrap_config: LanguageBootstrapRecipe<'a>,
) {
let config = load_config(bootstrap_config.name);
for adapter in bootstrap_config.adapters {
languages.register_lsp_adapter(config.name.clone(), adapter.clone());
}
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || {
let context_provider = if let Some(factory) = &bootstrap_config.context_provider {
Some(factory())
} else {
None
};
Ok((
config.clone(),
load_queries(bootstrap_config.name),
context_provider,
))
},
);
}
}
type Adapter = Arc<dyn LspAdapter>;
trait DynAdapter {
fn create<T: LspAdapter>(inner: T) -> Adapter;
}
impl DynAdapter for Adapter {
fn create<T: LspAdapter>(inner: T) -> Adapter {
Arc::new(inner)
}
}
pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) { pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) {
#[cfg(feature = "load-grammars")] #[cfg(feature = "load-grammars")]
languages.register_native_grammars([ languages.register_native_grammars([
@@ -55,134 +170,107 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
]); ]);
macro_rules! language { macro_rules! language {
($name:literal) => { ($($arg:expr), *) => {
let config = load_config($name); dsl::language(&languages, ($($arg), *))
languages.register_language( }
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
);
};
($name:literal, $adapters:expr) => {
let config = load_config($name);
// typeck helper
let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
for adapter in adapters {
languages.register_lsp_adapter(config.name.clone(), adapter);
}
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
);
};
($name:literal, $adapters:expr, $context_provider:expr) => {
let config = load_config($name);
// typeck helper
let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
for adapter in adapters {
languages.register_lsp_adapter(config.name.clone(), adapter);
}
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new($context_provider)),
))
},
);
};
} }
language!("bash", Vec::new(), bash_task_context()); language!("bash", bash_task_context);
language!("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]); language!("c", &[Adapter::create(c::CLspAdapter)]);
language!("cpp", vec![Arc::new(c::CLspAdapter)]); language!("cpp", &[Adapter::create(c::CLspAdapter)]);
language!( language!(
"css", "css",
vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),] &[Adapter::create(css::CssLspAdapter::new(
node_runtime.clone()
)),]
); );
language!("diff"); language!("diff");
language!("go", vec![Arc::new(go::GoLspAdapter)], GoContextProvider); language!(
language!("gomod", vec![Arc::new(go::GoLspAdapter)], GoContextProvider); "go",
&[Adapter::create(go::GoLspAdapter)],
GoContextProvider::default
);
language!(
"gomod",
&[Adapter::create(go::GoLspAdapter)],
GoContextProvider::default
);
language!( language!(
"gowork", "gowork",
vec![Arc::new(go::GoLspAdapter)], &[Adapter::create(go::GoLspAdapter)],
GoContextProvider GoContextProvider::default
); );
language!( language!(
"json", "json",
vec![ &[
Arc::new(json::JsonLspAdapter::new( Adapter::create(json::JsonLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
languages.clone(), languages.clone(),
)), )),
Arc::new(json::NodeVersionAdapter) Adapter::create(json::NodeVersionAdapter)
], ],
json_task_context() json_task_context
); );
language!( language!(
"jsonc", "jsonc",
vec![Arc::new(json::JsonLspAdapter::new( &[Adapter::create(json::JsonLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
languages.clone(), languages.clone(),
))], ))],
json_task_context() json_task_context
); );
language!("markdown"); language!("markdown");
language!("markdown-inline"); language!("markdown-inline");
language!( language!(
"python", "python",
vec![Arc::new(python::PythonLspAdapter::new( &[Adapter::create(python::PythonLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
))], ))],
PythonContextProvider PythonContextProvider::default
); );
language!( language!(
"rust", "rust",
vec![Arc::new(rust::RustLspAdapter)], &[Adapter::create(rust::RustLspAdapter)],
RustContextProvider RustContextProvider::default
); );
language!( language!(
"tsx", "tsx",
vec![ &[
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Adapter::create(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone())) Adapter::create(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
], ],
typescript_task_context() typescript_task_context
); );
language!( language!(
"typescript", "typescript",
vec![ &[
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Adapter::create(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone())) Adapter::create(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
], ],
typescript_task_context() typescript_task_context
); );
language!( language!(
"javascript", "javascript",
vec![ &[
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Adapter::create(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone())) Adapter::create(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
], ],
typescript_task_context() typescript_task_context
); );
language!( language!(
"jsdoc", "jsdoc",
vec![ &[
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone(),)), Adapter::create(typescript::TypeScriptLspAdapter::new(node_runtime.clone(),)),
Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone())) Adapter::create(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
] ]
); );
language!("regex"); language!("regex");
language!( language!(
"yaml", "yaml",
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))] &[Adapter::create(yaml::YamlLspAdapter::new(
node_runtime.clone()
))]
); );
// Register globally available language servers. // Register globally available language servers.

View File

@@ -227,6 +227,7 @@ async fn get_cached_server_binary(
} }
} }
#[derive(Default)]
pub(crate) struct PythonContextProvider; pub(crate) struct PythonContextProvider;
const PYTHON_UNITTEST_TARGET_TASK_VARIABLE: VariableName = const PYTHON_UNITTEST_TARGET_TASK_VARIABLE: VariableName =

View File

@@ -6,6 +6,7 @@ use futures::{io::BufReader, StreamExt};
use gpui::{AppContext, AsyncAppContext}; use gpui::{AppContext, AsyncAppContext};
use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
pub use language::*; pub use language::*;
use language_settings::all_language_settings;
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
use regex::Regex; use regex::Regex;
use smol::fs::{self, File}; use smol::fs::{self, File};
@@ -20,8 +21,6 @@ use std::{
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::{fs::remove_matching, maybe, ResultExt}; use util::{fs::remove_matching, maybe, ResultExt};
use crate::language_settings::language_settings;
pub struct RustLspAdapter; pub struct RustLspAdapter;
impl RustLspAdapter { impl RustLspAdapter {
@@ -363,6 +362,7 @@ impl LspAdapter for RustLspAdapter {
} }
} }
#[derive(Default)]
pub(crate) struct RustContextProvider; pub(crate) struct RustContextProvider;
const RUST_PACKAGE_TASK_VARIABLE: VariableName = const RUST_PACKAGE_TASK_VARIABLE: VariableName =
@@ -425,13 +425,13 @@ impl ContextProvider for RustContextProvider {
cx: &AppContext, cx: &AppContext,
) -> Option<TaskTemplates> { ) -> Option<TaskTemplates> {
const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
let package_to_run = language_settings(Some("Rust".into()), file.as_ref(), cx) let package_to_run = all_language_settings(file.as_ref(), cx)
.language(Some(&"Rust".into()))
.tasks .tasks
.variables .variables
.get(DEFAULT_RUN_NAME_STR) .get(DEFAULT_RUN_NAME_STR);
.cloned();
let run_task_args = if let Some(package_to_run) = package_to_run { let run_task_args = if let Some(package_to_run) = package_to_run {
vec!["run".into(), "-p".into(), package_to_run] vec!["run".into(), "-p".into(), package_to_run.clone()]
} else { } else {
vec!["run".into()] vec!["run".into()]
}; };

View File

@@ -101,7 +101,7 @@ impl LspAdapter for YamlLspAdapter {
let tab_size = cx.update(|cx| { let tab_size = cx.update(|cx| {
AllLanguageSettings::get(Some(location), cx) AllLanguageSettings::get(Some(location), cx)
.language(Some(location), Some(&"YAML".into()), cx) .language(Some(&"YAML".into()))
.tab_size .tab_size
})?; })?;
let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}}); let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}});

View File

@@ -102,8 +102,6 @@ impl<'a> MarkdownParser<'a> {
while !self.eof() { while !self.eof() {
if let Some(block) = self.parse_block().await { if let Some(block) = self.parse_block().await {
self.parsed.extend(block); self.parsed.extend(block);
} else {
self.cursor += 1;
} }
} }
self self
@@ -165,14 +163,20 @@ impl<'a> MarkdownParser<'a> {
let code_block = self.parse_code_block(language).await; let code_block = self.parse_code_block(language).await;
Some(vec![ParsedMarkdownElement::CodeBlock(code_block)]) Some(vec![ParsedMarkdownElement::CodeBlock(code_block)])
} }
_ => None, _ => {
self.cursor += 1;
None
}
}, },
Event::Rule => { Event::Rule => {
let source_range = source_range.clone(); let source_range = source_range.clone();
self.cursor += 1; self.cursor += 1;
Some(vec![ParsedMarkdownElement::HorizontalRule(source_range)]) Some(vec![ParsedMarkdownElement::HorizontalRule(source_range)])
} }
_ => None, _ => {
self.cursor += 1;
None
}
} }
} }
@@ -996,8 +1000,6 @@ Some other content
- Inner - Inner
- Inner - Inner
2. Goodbyte 2. Goodbyte
- Next item empty
-
* Last * Last
", ",
) )
@@ -1019,10 +1021,8 @@ Some other content
list_item(97..116, 3, Ordered(1), vec![p("Goodbyte", 100..108)]), list_item(97..116, 3, Ordered(1), vec![p("Goodbyte", 100..108)]),
list_item(117..124, 4, Unordered, vec![p("Inner", 119..124)]), list_item(117..124, 4, Unordered, vec![p("Inner", 119..124)]),
list_item(133..140, 4, Unordered, vec![p("Inner", 135..140)]), list_item(133..140, 4, Unordered, vec![p("Inner", 135..140)]),
list_item(143..159, 2, Ordered(2), vec![p("Goodbyte", 146..154)]), list_item(143..154, 2, Ordered(2), vec![p("Goodbyte", 146..154)]),
list_item(160..180, 3, Unordered, vec![p("Next item empty", 165..180)]), list_item(155..161, 1, Unordered, vec![p("Last", 157..161)]),
list_item(186..190, 3, Unordered, vec![]),
list_item(191..197, 1, Unordered, vec![p("Last", 193..197)]),
] ]
); );
} }

View File

@@ -189,7 +189,6 @@ pub struct MultiBufferSnapshot {
show_headers: bool, show_headers: bool,
} }
#[derive(Clone)]
pub struct ExcerptInfo { pub struct ExcerptInfo {
pub id: ExcerptId, pub id: ExcerptId,
pub buffer: BufferSnapshot, pub buffer: BufferSnapshot,
@@ -202,7 +201,6 @@ impl std::fmt::Debug for ExcerptInfo {
f.debug_struct(type_name::<Self>()) f.debug_struct(type_name::<Self>())
.field("id", &self.id) .field("id", &self.id)
.field("buffer_id", &self.buffer_id) .field("buffer_id", &self.buffer_id)
.field("path", &self.buffer.file().map(|f| f.path()))
.field("range", &self.range) .field("range", &self.range)
.finish() .finish()
} }
@@ -1778,7 +1776,7 @@ impl MultiBuffer {
&self, &self,
point: T, point: T,
cx: &'a AppContext, cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> { ) -> &'a LanguageSettings {
let mut language = None; let mut language = None;
let mut file = None; let mut file = None;
if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) { if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) {
@@ -1786,7 +1784,7 @@ impl MultiBuffer {
language = buffer.language_at(offset); language = buffer.language_at(offset);
file = buffer.file(); file = buffer.file();
} }
language_settings(language.map(|l| l.name()), file, cx) language_settings(language.as_ref(), file, cx)
} }
pub fn for_each_buffer(&self, mut f: impl FnMut(&Model<Buffer>)) { pub fn for_each_buffer(&self, mut f: impl FnMut(&Model<Buffer>)) {
@@ -3580,14 +3578,14 @@ impl MultiBufferSnapshot {
&'a self, &'a self,
point: T, point: T,
cx: &'a AppContext, cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> { ) -> &'a LanguageSettings {
let mut language = None; let mut language = None;
let mut file = None; let mut file = None;
if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { if let Some((buffer, offset)) = self.point_to_buffer_offset(point) {
language = buffer.language_at(offset); language = buffer.language_at(offset);
file = buffer.file(); file = buffer.file();
} }
language_settings(language.map(|l| l.name()), file, cx) language_settings(language, file, cx)
} }
pub fn language_scope_at<T: ToOffset>(&self, point: T) -> Option<LanguageScope> { pub fn language_scope_at<T: ToOffset>(&self, point: T) -> Option<LanguageScope> {

View File

@@ -293,6 +293,3 @@ pub fn local_tasks_file_relative_path() -> &'static Path {
pub fn local_vscode_tasks_file_relative_path() -> &'static Path { pub fn local_vscode_tasks_file_relative_path() -> &'static Path {
Path::new(".vscode/tasks.json") Path::new(".vscode/tasks.json")
} }
/// A default editorconfig file name to use when resolving project settings.
pub const EDITORCONFIG_NAME: &str = ".editorconfig";

View File

@@ -205,7 +205,7 @@ impl Prettier {
let params = buffer let params = buffer
.update(cx, |buffer, cx| { .update(cx, |buffer, cx| {
let buffer_language = buffer.language(); let buffer_language = buffer.language();
let language_settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); let language_settings = language_settings(buffer_language, buffer.file(), cx);
let prettier_settings = &language_settings.prettier; let prettier_settings = &language_settings.prettier;
anyhow::ensure!( anyhow::ensure!(
prettier_settings.allowed, prettier_settings.allowed,

View File

@@ -2303,9 +2303,7 @@ impl LspCommand for OnTypeFormatting {
.await?; .await?;
let options = buffer.update(&mut cx, |buffer, cx| { let options = buffer.update(&mut cx, |buffer, cx| {
lsp_formatting_options( lsp_formatting_options(language_settings(buffer.language(), buffer.file(), cx))
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx).as_ref(),
)
})?; })?;
Ok(Self { Ok(Self {

View File

@@ -30,7 +30,8 @@ use gpui::{
use http_client::HttpClient; use http_client::HttpClient;
use language::{ use language::{
language_settings::{ language_settings::{
language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, all_language_settings, language_settings, AllLanguageSettings, FormatOnSave, Formatter,
LanguageSettings, SelectedFormatter,
}, },
markdown, point_to_lsp, prepare_completion_documentation, markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
@@ -222,8 +223,7 @@ impl LocalLspStore {
})?; })?;
let settings = buffer.handle.update(&mut cx, |buffer, cx| { let settings = buffer.handle.update(&mut cx, |buffer, cx| {
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) language_settings(buffer.language(), buffer.file(), cx).clone()
.into_owned()
})?; })?;
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
@@ -280,7 +280,7 @@ impl LocalLspStore {
.zip(buffer.abs_path.as_ref()); .zip(buffer.abs_path.as_ref());
let prettier_settings = buffer.handle.read_with(&cx, |buffer, cx| { let prettier_settings = buffer.handle.read_with(&cx, |buffer, cx| {
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) language_settings(buffer.language(), buffer.file(), cx)
.prettier .prettier
.clone() .clone()
})?; })?;
@@ -1225,8 +1225,7 @@ impl LspStore {
}); });
let buffer_file = buffer.read(cx).file().cloned(); let buffer_file = buffer.read(cx).file().cloned();
let settings = let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
language_settings(Some(new_language.name()), buffer_file.as_ref(), cx).into_owned();
let buffer_file = File::from_dyn(buffer_file.as_ref()); let buffer_file = File::from_dyn(buffer_file.as_ref());
let worktree_id = if let Some(file) = buffer_file { let worktree_id = if let Some(file) = buffer_file {
@@ -1401,17 +1400,15 @@ impl LspStore {
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let buffer_file = File::from_dyn(buffer.file()); let buffer_file = File::from_dyn(buffer.file());
let buffer_language = buffer.language(); let buffer_language = buffer.language();
let settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); let settings = language_settings(buffer_language, buffer.file(), cx);
if let Some(language) = buffer_language { if let Some(language) = buffer_language {
if settings.enable_language_server { if settings.enable_language_server {
if let Some(file) = buffer_file { if let Some(file) = buffer_file {
language_servers_to_start.push((file.worktree.clone(), language.name())); language_servers_to_start.push((file.worktree.clone(), language.name()));
} }
} }
language_formatters_to_check.push(( language_formatters_to_check
buffer_file.map(|f| f.worktree_id(cx)), .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
settings.into_owned(),
));
} }
} }
@@ -1436,13 +1433,10 @@ impl LspStore {
}); });
if let Some((language, adapter)) = language { if let Some((language, adapter)) = language {
let worktree = self.worktree_for_id(worktree_id, cx).ok(); let worktree = self.worktree_for_id(worktree_id, cx).ok();
let root_file = worktree.as_ref().and_then(|worktree| { let file = worktree.as_ref().and_then(|tree| {
worktree tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
.update(cx, |tree, cx| tree.root_file(cx))
.map(|f| f as _)
}); });
let settings = language_settings(Some(language.name()), root_file.as_ref(), cx); if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
if !settings.enable_language_server {
language_servers_to_stop.push((worktree_id, started_lsp_name.clone())); language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
} else if let Some(worktree) = worktree { } else if let Some(worktree) = worktree {
let server_name = &adapter.name; let server_name = &adapter.name;
@@ -1759,9 +1753,10 @@ impl LspStore {
}) })
.filter(|_| { .filter(|_| {
maybe!({ maybe!({
let language = buffer.read(cx).language_at(position)?; let language_name = buffer.read(cx).language_at(position)?.name();
Some( Some(
language_settings(Some(language.name()), buffer.read(cx).file(), cx) AllLanguageSettings::get_global(cx)
.language(Some(&language_name))
.linked_edits, .linked_edits,
) )
}) == Some(true) }) == Some(true)
@@ -1855,14 +1850,11 @@ impl LspStore {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> { ) -> Task<Result<Option<Transaction>>> {
let options = buffer.update(cx, |buffer, cx| { let options = buffer.update(cx, |buffer, cx| {
lsp_command::lsp_formatting_options( lsp_command::lsp_formatting_options(language_settings(
language_settings( buffer.language_at(position).as_ref(),
buffer.language_at(position).map(|l| l.name()), buffer.file(),
buffer.file(), cx,
cx, ))
)
.as_ref(),
)
}); });
self.request_lsp( self.request_lsp(
buffer.clone(), buffer.clone(),
@@ -5296,16 +5288,23 @@ impl LspStore {
}) })
} }
fn language_settings<'a>(
&'a self,
worktree: &'a Model<Worktree>,
language: &LanguageName,
cx: &'a mut ModelContext<Self>,
) -> &'a LanguageSettings {
let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
all_language_settings(root_file.map(|f| f as _).as_ref(), cx).language(Some(language))
}
pub fn start_language_servers( pub fn start_language_servers(
&mut self, &mut self,
worktree: &Model<Worktree>, worktree: &Model<Worktree>,
language: LanguageName, language: LanguageName,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
let root_file = worktree let settings = self.language_settings(worktree, &language, cx);
.update(cx, |tree, cx| tree.root_file(cx))
.map(|f| f as _);
let settings = language_settings(Some(language.clone()), root_file.as_ref(), cx);
if !settings.enable_language_server || self.mode.is_remote() { if !settings.enable_language_server || self.mode.is_remote() {
return; return;
} }
@@ -6058,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" => { "textDocument/rangeFormatting" => {
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
if let Some(server) = this.language_server_for_id(server_id) if let Some(server) = this.language_server_for_id(server_id)

View File

@@ -1243,10 +1243,6 @@ impl Project {
self.client.clone() self.client.clone()
} }
pub fn ssh_client(&self) -> Option<Model<SshRemoteClient>> {
self.ssh_client.clone()
}
pub fn user_store(&self) -> Model<UserStore> { pub fn user_store(&self) -> Model<UserStore> {
self.user_store.clone() self.user_store.clone()
} }

View File

@@ -5,7 +5,7 @@ use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, M
use language::LanguageServerName; use language::LanguageServerName;
use paths::{ use paths::{
local_settings_file_relative_path, local_tasks_file_relative_path, local_settings_file_relative_path, local_tasks_file_relative_path,
local_vscode_tasks_file_relative_path, EDITORCONFIG_NAME, local_vscode_tasks_file_relative_path,
}; };
use rpc::{proto, AnyProtoClient, TypedEnvelope}; use rpc::{proto, AnyProtoClient, TypedEnvelope};
use schemars::JsonSchema; use schemars::JsonSchema;
@@ -287,29 +287,14 @@ impl SettingsObserver {
let store = cx.global::<SettingsStore>(); let store = cx.global::<SettingsStore>();
for worktree in self.worktree_store.read(cx).worktrees() { for worktree in self.worktree_store.read(cx).worktrees() {
let worktree_id = worktree.read(cx).id().to_proto(); let worktree_id = worktree.read(cx).id().to_proto();
for (path, content) in store.local_settings(worktree.read(cx).id()) { for (path, kind, content) in store.local_settings(worktree.read(cx).id()) {
downstream_client downstream_client
.send(proto::UpdateWorktreeSettings { .send(proto::UpdateWorktreeSettings {
project_id, project_id,
worktree_id, worktree_id,
path: path.to_string_lossy().into(), path: path.to_string_lossy().into(),
content: Some(content), content: Some(content),
kind: Some( kind: Some(local_settings_kind_to_proto(kind).into()),
local_settings_kind_to_proto(LocalSettingsKind::Settings).into(),
),
})
.log_err();
}
for (path, content, _) in store.local_editorconfig_settings(worktree.read(cx).id()) {
downstream_client
.send(proto::UpdateWorktreeSettings {
project_id,
worktree_id,
path: path.to_string_lossy().into(),
content: Some(content),
kind: Some(
local_settings_kind_to_proto(LocalSettingsKind::Editorconfig).into(),
),
}) })
.log_err(); .log_err();
} }
@@ -468,11 +453,6 @@ impl SettingsObserver {
.unwrap(), .unwrap(),
); );
(settings_dir, LocalSettingsKind::Tasks) (settings_dir, LocalSettingsKind::Tasks)
} else if path.ends_with(EDITORCONFIG_NAME) {
let Some(settings_dir) = path.parent().map(Arc::from) else {
continue;
};
(settings_dir, LocalSettingsKind::Editorconfig)
} else { } else {
continue; continue;
}; };

View File

@@ -4,9 +4,7 @@ use futures::{future, StreamExt};
use gpui::{AppContext, SemanticVersion, UpdateGlobal}; use gpui::{AppContext, SemanticVersion, UpdateGlobal};
use http_client::Url; use http_client::Url;
use language::{ use language::{
language_settings::{ language_settings::{language_settings, AllLanguageSettings, LanguageSettingsContent},
language_settings, AllLanguageSettings, LanguageSettingsContent, SoftWrap,
},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter, tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint, LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
}; };
@@ -17,7 +15,7 @@ use serde_json::json;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::os; use std::os;
use std::{mem, num::NonZeroU32, ops::Range, task::Poll}; use std::{mem, ops::Range, task::Poll};
use task::{ResolvedTask, TaskContext}; use task::{ResolvedTask, TaskContext};
use unindent::Unindent as _; use unindent::Unindent as _;
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _}; use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _};
@@ -93,107 +91,6 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
}); });
} }
#[gpui::test]
async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
init_test(cx);
let dir = temp_tree(json!({
".editorconfig": r#"
root = true
[*.rs]
indent_style = tab
indent_size = 3
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
[*.js]
tab_width = 10
"#,
".zed": {
"settings.json": r#"{
"tab_size": 8,
"hard_tabs": false,
"ensure_final_newline_on_save": false,
"remove_trailing_whitespace_on_save": false,
"preferred_line_length": 64,
"soft_wrap": "editor_width"
}"#,
},
"a.rs": "fn a() {\n A\n}",
"b": {
".editorconfig": r#"
[*.rs]
indent_size = 2
max_line_length = off
"#,
"b.rs": "fn b() {\n B\n}",
},
"c.js": "def c\n C\nend",
"README.json": "tabs are better\n",
}));
let path = dir.path();
let fs = FakeFs::new(cx.executor());
fs.insert_tree_from_real_fs(path, path).await;
let project = Project::test(fs, [path], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(js_lang());
language_registry.add(json_lang());
language_registry.add(rust_lang());
let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
cx.executor().run_until_parked();
cx.update(|cx| {
let tree = worktree.read(cx);
let settings_for = |path: &str| {
let file_entry = tree.entry_for_path(path).unwrap().clone();
let file = File::for_entry(file_entry, worktree.clone());
let file_language = project
.read(cx)
.languages()
.language_for_file_path(file.path.as_ref());
let file_language = cx
.background_executor()
.block(file_language)
.expect("Failed to get file language");
let file = file as _;
language_settings(Some(file_language.name()), Some(&file), cx).into_owned()
};
let settings_a = settings_for("a.rs");
let settings_b = settings_for("b/b.rs");
let settings_c = settings_for("c.js");
let settings_readme = settings_for("README.json");
// .editorconfig overrides .zed/settings
assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3));
assert_eq!(settings_a.hard_tabs, true);
assert_eq!(settings_a.ensure_final_newline_on_save, true);
assert_eq!(settings_a.remove_trailing_whitespace_on_save, true);
assert_eq!(settings_a.preferred_line_length, 80);
// "max_line_length" also sets "soft_wrap"
assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength);
// .editorconfig in b/ overrides .editorconfig in root
assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2));
// "indent_size" is not set, so "tab_width" is used
assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10));
// When max_line_length is "off", default to .zed/settings.json
assert_eq!(settings_b.preferred_line_length, 64);
assert_eq!(settings_b.soft_wrap, SoftWrap::EditorWidth);
// README.md should not be affected by .editorconfig's globe "*.rs"
assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8));
});
}
#[gpui::test] #[gpui::test]
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
@@ -249,16 +146,26 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
.update(|cx| { .update(|cx| {
let tree = worktree.read(cx); let tree = worktree.read(cx);
let file_a = File::for_entry( let settings_a = language_settings(
tree.entry_for_path("a/a.rs").unwrap().clone(), None,
worktree.clone(), Some(
) as _; &(File::for_entry(
let settings_a = language_settings(None, Some(&file_a), cx); tree.entry_for_path("a/a.rs").unwrap().clone(),
let file_b = File::for_entry( worktree.clone(),
tree.entry_for_path("b/b.rs").unwrap().clone(), ) as _),
worktree.clone(), ),
) as _; cx,
let settings_b = language_settings(None, Some(&file_b), cx); );
let settings_b = language_settings(
None,
Some(
&(File::for_entry(
tree.entry_for_path("b/b.rs").unwrap().clone(),
worktree.clone(),
) as _),
),
cx,
);
assert_eq!(settings_a.tab_size.get(), 8); assert_eq!(settings_a.tab_size.get(), 8);
assert_eq!(settings_b.tab_size.get(), 2); assert_eq!(settings_b.tab_size.get(), 2);

View File

@@ -91,6 +91,7 @@ struct EditState {
entry_id: ProjectEntryId, entry_id: ProjectEntryId,
is_new_entry: bool, is_new_entry: bool,
is_dir: bool, is_dir: bool,
is_symlink: bool,
depth: usize, depth: usize,
processing_filename: Option<String>, processing_filename: Option<String>,
} }
@@ -986,6 +987,7 @@ impl ProjectPanel {
is_new_entry: true, is_new_entry: true,
is_dir, is_dir,
processing_filename: None, processing_filename: None,
is_symlink: false,
depth: 0, depth: 0,
}); });
self.filename_editor.update(cx, |editor, cx| { self.filename_editor.update(cx, |editor, cx| {
@@ -1025,6 +1027,7 @@ impl ProjectPanel {
is_new_entry: false, is_new_entry: false,
is_dir: entry.is_dir(), is_dir: entry.is_dir(),
processing_filename: None, processing_filename: None,
is_symlink: entry.is_symlink,
depth: 0, depth: 0,
}); });
let file_name = entry let file_name = entry
@@ -1530,15 +1533,16 @@ impl ProjectPanel {
fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) { fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_sub_entry(cx) { if let Some((worktree, entry)) = self.selected_sub_entry(cx) {
let abs_path = match &entry.canonical_path { let abs_path = worktree.abs_path().join(&entry.path);
Some(canonical_path) => Some(canonical_path.to_path_buf()),
None => worktree.absolutize(&entry.path).ok(),
};
let working_directory = if entry.is_dir() { let working_directory = if entry.is_dir() {
abs_path Some(abs_path)
} else { } else {
abs_path.and_then(|path| Some(path.parent()?.to_path_buf())) if entry.is_symlink {
abs_path.canonicalize().ok()
} else {
Some(abs_path)
}
.and_then(|path| Some(path.parent()?.to_path_buf()))
}; };
if let Some(working_directory) = working_directory { if let Some(working_directory) = working_directory {
cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone()) cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone())
@@ -1826,6 +1830,7 @@ impl ProjectPanel {
.unwrap_or_default(); .unwrap_or_default();
if let Some(edit_state) = &mut self.edit_state { if let Some(edit_state) = &mut self.edit_state {
if edit_state.entry_id == entry.id { if edit_state.entry_id == entry.id {
edit_state.is_symlink = entry.is_symlink;
edit_state.depth = depth; edit_state.depth = depth;
} }
} }
@@ -1856,6 +1861,7 @@ impl ProjectPanel {
is_private: false, is_private: false,
git_status: entry.git_status, git_status: entry.git_status,
canonical_path: entry.canonical_path.clone(), canonical_path: entry.canonical_path.clone(),
is_symlink: entry.is_symlink,
char_bag: entry.char_bag, char_bag: entry.char_bag,
is_fifo: entry.is_fifo, is_fifo: entry.is_fifo,
}); });
@@ -1914,7 +1920,7 @@ impl ProjectPanel {
let width_estimate = item_width_estimate( let width_estimate = item_width_estimate(
depth, depth,
path.to_string_lossy().chars().count(), path.to_string_lossy().chars().count(),
entry.canonical_path.is_some(), entry.is_symlink,
); );
match max_width_item.as_mut() { match max_width_item.as_mut() {

View File

@@ -12,7 +12,6 @@ message Envelope {
uint32 id = 1; uint32 id = 1;
optional uint32 responding_to = 2; optional uint32 responding_to = 2;
optional PeerId original_sender_id = 3; optional PeerId original_sender_id = 3;
optional uint32 ack_id = 266;
oneof payload { oneof payload {
Hello hello = 4; Hello hello = 4;
@@ -296,9 +295,7 @@ message Envelope {
OpenServerSettings open_server_settings = 263; OpenServerSettings open_server_settings = 263;
GetPermalinkToLine get_permalink_to_line = 264; GetPermalinkToLine get_permalink_to_line = 264;
GetPermalinkToLineResponse get_permalink_to_line_response = 265; GetPermalinkToLineResponse get_permalink_to_line_response = 265; // current max
FlushBufferedMessages flush_buffered_messages = 267;
} }
reserved 87 to 88; reserved 87 to 88;
@@ -1870,13 +1867,12 @@ message Entry {
string path = 3; string path = 3;
uint64 inode = 4; uint64 inode = 4;
Timestamp mtime = 5; Timestamp mtime = 5;
bool is_symlink = 6;
bool is_ignored = 7; bool is_ignored = 7;
bool is_external = 8; bool is_external = 8;
reserved 6;
optional GitStatus git_status = 9; optional GitStatus git_status = 9;
bool is_fifo = 10; bool is_fifo = 10;
optional uint64 size = 11; optional uint64 size = 11;
optional string canonical_path = 12;
} }
message RepositoryEntry { message RepositoryEntry {
@@ -2525,6 +2521,3 @@ message GetPermalinkToLine {
message GetPermalinkToLineResponse { message GetPermalinkToLineResponse {
string permalink = 1; string permalink = 1;
} }
message FlushBufferedMessages {}
message FlushBufferedMessagesResponse {}

View File

@@ -32,7 +32,6 @@ macro_rules! messages {
responding_to, responding_to,
original_sender_id, original_sender_id,
payload: Some(envelope::Payload::$name(self)), payload: Some(envelope::Payload::$name(self)),
ack_id: None,
} }
} }

View File

@@ -372,7 +372,6 @@ messages!(
(OpenServerSettings, Foreground), (OpenServerSettings, Foreground),
(GetPermalinkToLine, Foreground), (GetPermalinkToLine, Foreground),
(GetPermalinkToLineResponse, Foreground), (GetPermalinkToLineResponse, Foreground),
(FlushBufferedMessages, Foreground),
); );
request_messages!( request_messages!(
@@ -499,7 +498,6 @@ request_messages!(
(RemoveWorktree, Ack), (RemoveWorktree, Ack),
(OpenServerSettings, OpenBufferResponse), (OpenServerSettings, OpenBufferResponse),
(GetPermalinkToLine, GetPermalinkToLineResponse), (GetPermalinkToLine, GetPermalinkToLineResponse),
(FlushBufferedMessages, Ack),
); );
entity_messages!( entity_messages!(

View File

@@ -175,7 +175,7 @@ impl Render for SshPrompt {
.child( .child(
h_flex() h_flex()
.p_2() .p_2()
.flex() .flex_wrap()
.child(if self.error_message.is_some() { .child(if self.error_message.is_some() {
Icon::new(IconName::XCircle) Icon::new(IconName::XCircle)
.size(IconSize::Medium) .size(IconSize::Medium)
@@ -195,7 +195,6 @@ impl Render for SshPrompt {
}) })
.child( .child(
div() div()
.ml_1()
.text_ellipsis() .text_ellipsis()
.overflow_x_hidden() .overflow_x_hidden()
.when_some(self.error_message.as_ref(), |el, error| { .when_some(self.error_message.as_ref(), |el, error| {
@@ -206,7 +205,7 @@ impl Render for SshPrompt {
|el| { |el| {
el.child( el.child(
Label::new(format!( Label::new(format!(
"{}", "{}",
self.status_message.clone().unwrap() self.status_message.clone().unwrap()
)) ))
.size(LabelSize::Small), .size(LabelSize::Small),

View File

@@ -19,7 +19,6 @@ test-support = ["fs/test-support"]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
async-trait.workspace = true
collections.workspace = true collections.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true

View File

@@ -2,6 +2,7 @@ use anyhow::Result;
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use prost::Message as _; use prost::Message as _;
use rpc::proto::Envelope; use rpc::proto::Envelope;
use std::mem::size_of;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MessageId(pub u32); pub struct MessageId(pub u32);
@@ -29,10 +30,8 @@ pub async fn read_message<S: AsyncRead + Unpin>(
) -> Result<Envelope> { ) -> Result<Envelope> {
buffer.resize(MESSAGE_LEN_SIZE, 0); buffer.resize(MESSAGE_LEN_SIZE, 0);
stream.read_exact(buffer).await?; stream.read_exact(buffer).await?;
let len = message_len_from_buffer(buffer); let len = message_len_from_buffer(buffer);
let result = read_message_with_len(stream, buffer, len).await; read_message_with_len(stream, buffer, len).await
result
} }
pub async fn write_message<S: AsyncWrite + Unpin>( pub async fn write_message<S: AsyncWrite + Unpin>(

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use fs::{FakeFs, Fs};
use gpui::{Context, Model, TestAppContext}; use gpui::{Context, Model, TestAppContext};
use http_client::{BlockedHttpClient, FakeHttpClient}; use http_client::{BlockedHttpClient, FakeHttpClient};
use language::{ use language::{
language_settings::{language_settings, AllLanguageSettings}, language_settings::{all_language_settings, AllLanguageSettings},
Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName, Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName,
LineEnding, LineEnding,
}; };
@@ -208,7 +208,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| { server_cx.read(|cx| {
assert_eq!( assert_eq!(
AllLanguageSettings::get_global(cx) AllLanguageSettings::get_global(cx)
.language(None, Some(&"Rust".into()), cx) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["from-local-settings".to_string()] ["from-local-settings".to_string()]
) )
@@ -228,7 +228,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| { server_cx.read(|cx| {
assert_eq!( assert_eq!(
AllLanguageSettings::get_global(cx) AllLanguageSettings::get_global(cx)
.language(None, Some(&"Rust".into()), cx) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["from-server-settings".to_string()] ["from-server-settings".to_string()]
) )
@@ -287,7 +287,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
}), }),
cx cx
) )
.language(None, Some(&"Rust".into()), cx) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["override-rust-analyzer".to_string()] ["override-rust-analyzer".to_string()]
) )
@@ -296,7 +296,9 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
cx.read(|cx| { cx.read(|cx| {
let file = buffer.read(cx).file(); let file = buffer.read(cx).file();
assert_eq!( assert_eq!(
language_settings(Some("Rust".into()), file, cx).language_servers, all_language_settings(file, cx)
.language(Some(&"Rust".into()))
.language_servers,
["override-rust-analyzer".to_string()] ["override-rust-analyzer".to_string()]
) )
}); });
@@ -377,7 +379,9 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
cx.read(|cx| { cx.read(|cx| {
let file = buffer.read(cx).file(); let file = buffer.read(cx).file();
assert_eq!( assert_eq!(
language_settings(Some("Rust".into()), file, cx).language_servers, all_language_settings(file, cx)
.language(Some(&"Rust".into()))
.language_servers,
["rust-analyzer".to_string()] ["rust-analyzer".to_string()]
) )
}); });
@@ -637,47 +641,6 @@ async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut Test
}) })
} }
#[gpui::test(iterations = 20)]
async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let (project, _headless, fs) = init_test(cx, server_cx).await;
let (worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx)
})
.await
.unwrap();
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
})
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
let ix = buffer.text().find('1').unwrap();
buffer.edit([(ix..ix + 1, "100")], None, cx);
});
let client = cx.read(|cx| project.read(cx).ssh_client().unwrap());
client
.update(cx, |client, cx| client.simulate_disconnect(cx))
.detach();
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
assert_eq!(
fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
"fn one() -> usize { 100 }"
);
}
fn init_logger() { fn init_logger() {
if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok(); env_logger::try_init().ok();
@@ -688,9 +651,9 @@ async fn init_test(
cx: &mut TestAppContext, cx: &mut TestAppContext,
server_cx: &mut TestAppContext, server_cx: &mut TestAppContext,
) -> (Model<Project>, Model<HeadlessProject>, Arc<FakeFs>) { ) -> (Model<Project>, Model<HeadlessProject>, Arc<FakeFs>) {
let (ssh_remote_client, ssh_server_client) = SshRemoteClient::fake(cx, server_cx);
init_logger(); init_logger();
let (forwarder, ssh_server_client) = SshRemoteClient::fake_server(cx, server_cx);
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", "/code",
@@ -731,9 +694,8 @@ async fn init_test(
cx, cx,
) )
}); });
let project = build_project(ssh_remote_client, cx);
let ssh = SshRemoteClient::fake_client(forwarder, cx).await;
let project = build_project(ssh, cx);
project project
.update(cx, { .update(cx, {
let headless = headless.clone(); let headless = headless.clone();

View File

@@ -12,7 +12,6 @@ use language::LanguageRegistry;
use node_runtime::{NodeBinaryOptions, NodeRuntime}; use node_runtime::{NodeBinaryOptions, NodeRuntime};
use paths::logs_dir; use paths::logs_dir;
use project::project_settings::ProjectSettings; use project::project_settings::ProjectSettings;
use remote::proxy::ProxyLaunchError; use remote::proxy::ProxyLaunchError;
use remote::ssh_session::ChannelClient; use remote::ssh_session::ChannelClient;
use remote::{ use remote::{
@@ -214,27 +213,19 @@ fn start_server(
let mut input_buffer = Vec::new(); let mut input_buffer = Vec::new();
let mut output_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 { loop {
select_biased! { select_biased! {
_ = app_quit_rx.next().fuse() => { _ = app_quit_rx.next().fuse() => {
return anyhow::Ok(()); return anyhow::Ok(());
} }
stdin_message = stdin_msg_rx.next().fuse() => { stdin_message = read_message(&mut stdin_stream, &mut input_buffer).fuse() => {
let Some(message) = stdin_message else { let message = match stdin_message {
log::warn!("error reading message on stdin. exiting."); Ok(message) => message,
break; Err(error) => {
log::warn!("error reading message on stdin: {}. exiting.", error);
break;
}
}; };
if let Err(error) = incoming_tx.unbounded_send(message) { if let Err(error) = incoming_tx.unbounded_send(message) {
log::error!("failed to send message to application: {:?}. exiting.", error); log::error!("failed to send message to application: {:?}. exiting.", error);
@@ -279,7 +270,7 @@ fn start_server(
}) })
.detach(); .detach();
ChannelClient::new(incoming_rx, outgoing_tx, cx, "server") ChannelClient::new(incoming_rx, outgoing_tx, cx)
} }
fn init_paths() -> anyhow::Result<()> { fn init_paths() -> anyhow::Result<()> {

View File

@@ -17,7 +17,8 @@ use editor::{
use futures::io::BufReader; use futures::io::BufReader;
use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _}; use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _};
use gpui::{ 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 language::Point;
use project::Fs; use project::Fs;
@@ -148,21 +149,23 @@ impl EditorBlock {
.w(text_line_height) .w(text_line_height)
.h(text_line_height) .h(text_line_height)
.child( .child(
IconButton::new("close_output_area", IconName::Close) IconButton::new(
.icon_size(IconSize::Small) ("close_output_area", EntityId::from(cx.block_id)),
.icon_color(Color::Muted) IconName::Close,
.size(ButtonSize::Compact) )
.shape(IconButtonShape::Square) .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Close output area", cx)) .icon_color(Color::Muted)
.on_click(move |_, cx| { .size(ButtonSize::Compact)
if let BlockId::Custom(block_id) = block_id { .shape(IconButtonShape::Square)
(on_close)(block_id, cx) .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() div()
.id(cx.block_id)
.flex() .flex()
.items_start() .items_start()
.min_h(text_line_height) .min_h(text_line_height)

View File

@@ -44,12 +44,8 @@ impl ReqwestClient {
let mut client = reqwest::Client::builder() let mut client = reqwest::Client::builder()
.use_rustls_tls() .use_rustls_tls()
.default_headers(map); .default_headers(map);
if let Some(proxy) = proxy.clone().and_then(|proxy_uri| { if let Some(proxy) = proxy.clone() {
reqwest::Proxy::all(proxy_uri.to_string()) client = client.proxy(reqwest::Proxy::all(proxy.to_string())?);
.inspect_err(|e| log::error!("Failed to parse proxy URI {}: {}", proxy_uri, e))
.ok()
}) {
client = client.proxy(proxy);
} }
let client = client.build()?; let client = client.build()?;
let mut client: ReqwestClient = client.into(); let mut client: ReqwestClient = client.into();
@@ -236,47 +232,3 @@ impl http_client::HttpClient for ReqwestClient {
.boxed() .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();
}
}

View File

@@ -18,7 +18,6 @@ test-support = ["gpui/test-support", "fs/test-support"]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true
ec4rs.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true

View File

@@ -1,10 +1,9 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{btree_map, hash_map, BTreeMap, HashMap}; use collections::{btree_map, hash_map, BTreeMap, HashMap};
use ec4rs::{ConfigParser, PropertiesSource, Section};
use fs::Fs; use fs::Fs;
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt}; use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal}; use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME}; use paths::local_settings_file_relative_path;
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -13,14 +12,12 @@ use std::{
fmt::Debug, fmt::Debug,
ops::Range, ops::Range,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::{self, FromStr}, str,
sync::{Arc, LazyLock}, sync::{Arc, LazyLock},
}; };
use tree_sitter::Query; use tree_sitter::Query;
use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
pub type EditorconfigProperties = ec4rs::Properties;
use crate::{SettingsJsonSchemaParams, WorktreeId}; use crate::{SettingsJsonSchemaParams, WorktreeId};
/// A value that can be defined as a user setting. /// A value that can be defined as a user setting.
@@ -170,8 +167,8 @@ pub struct SettingsStore {
raw_user_settings: serde_json::Value, raw_user_settings: serde_json::Value,
raw_server_settings: Option<serde_json::Value>, raw_server_settings: Option<serde_json::Value>,
raw_extension_settings: serde_json::Value, raw_extension_settings: serde_json::Value,
raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>, raw_local_settings:
raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>, BTreeMap<(WorktreeId, Arc<Path>), HashMap<LocalSettingsKind, serde_json::Value>>,
tab_size_callback: Option<( tab_size_callback: Option<(
TypeId, TypeId,
Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>, Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
@@ -182,26 +179,6 @@ pub struct SettingsStore {
>, >,
} }
#[derive(Clone)]
pub struct Editorconfig {
pub is_root: bool,
pub sections: SmallVec<[Section; 5]>,
}
impl FromStr for Editorconfig {
type Err = anyhow::Error;
fn from_str(contents: &str) -> Result<Self, Self::Err> {
let parser = ConfigParser::new_buffered(contents.as_bytes())
.context("creating editorconfig parser")?;
let is_root = parser.is_root;
let sections = parser
.collect::<Result<SmallVec<_>, _>>()
.context("parsing editorconfig sections")?;
Ok(Self { is_root, sections })
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum LocalSettingsKind { pub enum LocalSettingsKind {
Settings, Settings,
@@ -249,7 +226,6 @@ impl SettingsStore {
raw_server_settings: None, raw_server_settings: None,
raw_extension_settings: serde_json::json!({}), raw_extension_settings: serde_json::json!({}),
raw_local_settings: Default::default(), raw_local_settings: Default::default(),
raw_editorconfig_settings: BTreeMap::default(),
tab_size_callback: Default::default(), tab_size_callback: Default::default(),
setting_file_updates_tx, setting_file_updates_tx,
_setting_file_updates: cx.spawn(|cx| async move { _setting_file_updates: cx.spawn(|cx| async move {
@@ -591,91 +567,33 @@ impl SettingsStore {
settings_content: Option<&str>, settings_content: Option<&str>,
cx: &mut AppContext, cx: &mut AppContext,
) -> std::result::Result<(), InvalidSettingsError> { ) -> std::result::Result<(), InvalidSettingsError> {
let mut zed_settings_changed = false; debug_assert!(
match ( kind != LocalSettingsKind::Tasks,
kind, "Attempted to submit tasks into the settings store"
settings_content );
.map(|content| content.trim())
.filter(|content| !content.is_empty()), let raw_local_settings = self
) { .raw_local_settings
(LocalSettingsKind::Tasks, _) => { .entry((root_id, directory_path.clone()))
return Err(InvalidSettingsError::Tasks { .or_default();
message: "Attempted to submit tasks into the settings store".to_string(), let changed = if settings_content.is_some_and(|content| !content.is_empty()) {
}) let new_contents =
} parse_json_with_comments(settings_content.unwrap()).map_err(|e| {
(LocalSettingsKind::Settings, None) => { InvalidSettingsError::LocalSettings {
zed_settings_changed = self
.raw_local_settings
.remove(&(root_id, directory_path.clone()))
.is_some()
}
(LocalSettingsKind::Editorconfig, None) => {
self.raw_editorconfig_settings
.remove(&(root_id, directory_path.clone()));
}
(LocalSettingsKind::Settings, Some(settings_contents)) => {
let new_settings = parse_json_with_comments::<serde_json::Value>(settings_contents)
.map_err(|e| InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()), path: directory_path.join(local_settings_file_relative_path()),
message: e.to_string(), message: e.to_string(),
})?;
match self
.raw_local_settings
.entry((root_id, directory_path.clone()))
{
btree_map::Entry::Vacant(v) => {
v.insert(new_settings);
zed_settings_changed = true;
} }
btree_map::Entry::Occupied(mut o) => { })?;
if o.get() != &new_settings { if Some(&new_contents) == raw_local_settings.get(&kind) {
o.insert(new_settings); false
zed_settings_changed = true; } else {
} raw_local_settings.insert(kind, new_contents);
} true
}
}
(LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
match self
.raw_editorconfig_settings
.entry((root_id, directory_path.clone()))
{
btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
Ok(new_contents) => {
v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
}
Err(e) => {
v.insert((editorconfig_contents.to_owned(), None));
return Err(InvalidSettingsError::Editorconfig {
message: e.to_string(),
path: directory_path.join(EDITORCONFIG_NAME),
});
}
},
btree_map::Entry::Occupied(mut o) => {
if o.get().0 != editorconfig_contents {
match editorconfig_contents.parse() {
Ok(new_contents) => {
o.insert((
editorconfig_contents.to_owned(),
Some(new_contents),
));
}
Err(e) => {
o.insert((editorconfig_contents.to_owned(), None));
return Err(InvalidSettingsError::Editorconfig {
message: e.to_string(),
path: directory_path.join(EDITORCONFIG_NAME),
});
}
}
}
}
}
} }
} else {
raw_local_settings.remove(&kind).is_some()
}; };
if changed {
if zed_settings_changed {
self.recompute_values(Some((root_id, &directory_path)), cx)?; self.recompute_values(Some((root_id, &directory_path)), cx)?;
} }
Ok(()) Ok(())
@@ -687,10 +605,13 @@ impl SettingsStore {
cx: &mut AppContext, cx: &mut AppContext,
) -> Result<()> { ) -> Result<()> {
let settings: serde_json::Value = serde_json::to_value(content)?; let settings: serde_json::Value = serde_json::to_value(content)?;
anyhow::ensure!(settings.is_object(), "settings must be an object"); if settings.is_object() {
self.raw_extension_settings = settings; self.raw_extension_settings = settings;
self.recompute_values(None, cx)?; self.recompute_values(None, cx)?;
Ok(()) Ok(())
} else {
Err(anyhow!("settings must be an object"))
}
} }
/// Add or remove a set of local settings via a JSON string. /// Add or remove a set of local settings via a JSON string.
@@ -704,7 +625,7 @@ impl SettingsStore {
pub fn local_settings( pub fn local_settings(
&self, &self,
root_id: WorktreeId, root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, String)> { ) -> impl '_ + Iterator<Item = (Arc<Path>, LocalSettingsKind, String)> {
self.raw_local_settings self.raw_local_settings
.range( .range(
(root_id, Path::new("").into()) (root_id, Path::new("").into())
@@ -713,23 +634,11 @@ impl SettingsStore {
Path::new("").into(), Path::new("").into(),
), ),
) )
.map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap())) .flat_map(|((_, path), content)| {
} content.iter().filter_map(|(&kind, raw_content)| {
let parsed_content = serde_json::to_string(raw_content).log_err()?;
pub fn local_editorconfig_settings( Some((path.clone(), kind, parsed_content))
&self, })
root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
self.raw_editorconfig_settings
.range(
(root_id, Path::new("").into())
..(
WorktreeId::from_usize(root_id.to_usize() + 1),
Path::new("").into(),
),
)
.map(|((_, path), (content, parsed_content))| {
(path.clone(), content.clone(), parsed_content.clone())
}) })
} }
@@ -844,7 +753,7 @@ impl SettingsStore {
&mut self, &mut self,
changed_local_path: Option<(WorktreeId, &Path)>, changed_local_path: Option<(WorktreeId, &Path)>,
cx: &mut AppContext, cx: &mut AppContext,
) -> std::result::Result<(), InvalidSettingsError> { ) -> Result<(), InvalidSettingsError> {
// Reload the global and local values for every setting. // Reload the global and local values for every setting.
let mut project_settings_stack = Vec::<DeserializedSetting>::new(); let mut project_settings_stack = Vec::<DeserializedSetting>::new();
let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new(); let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
@@ -910,90 +819,69 @@ impl SettingsStore {
paths_stack.clear(); paths_stack.clear();
project_settings_stack.clear(); project_settings_stack.clear();
for ((root_id, directory_path), local_settings) in &self.raw_local_settings { for ((root_id, directory_path), local_settings) in &self.raw_local_settings {
// Build a stack of all of the local values for that setting. if let Some(local_settings) = local_settings.get(&LocalSettingsKind::Settings) {
while let Some(prev_entry) = paths_stack.last() { // Build a stack of all of the local values for that setting.
if let Some((prev_root_id, prev_path)) = prev_entry { while let Some(prev_entry) = paths_stack.last() {
if root_id != prev_root_id || !directory_path.starts_with(prev_path) { if let Some((prev_root_id, prev_path)) = prev_entry {
paths_stack.pop(); if root_id != prev_root_id || !directory_path.starts_with(prev_path) {
project_settings_stack.pop(); paths_stack.pop();
continue; project_settings_stack.pop();
continue;
}
} }
break;
} }
break;
}
match setting_value.deserialize_setting(local_settings) { match setting_value.deserialize_setting(local_settings) {
Ok(local_settings) => { Ok(local_settings) => {
paths_stack.push(Some((*root_id, directory_path.as_ref()))); paths_stack.push(Some((*root_id, directory_path.as_ref())));
project_settings_stack.push(local_settings); project_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local // If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory. // settings for any path outside of that directory.
if changed_local_path.map_or( if changed_local_path.map_or(
false, false,
|(changed_root_id, changed_local_path)| { |(changed_root_id, changed_local_path)| {
*root_id != changed_root_id *root_id != changed_root_id
|| !directory_path.starts_with(changed_local_path) || !directory_path.starts_with(changed_local_path)
},
) {
continue;
}
if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
server: server_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
}, },
cx, ) {
) continue;
.log_err() }
{
setting_value.set_local_value(*root_id, directory_path.clone(), value); if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
server: server_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
},
cx,
)
.log_err()
{
setting_value.set_local_value(
*root_id,
directory_path.clone(),
value,
);
}
}
Err(error) => {
return Err(InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: error.to_string(),
});
} }
}
Err(error) => {
return Err(InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: error.to_string(),
});
} }
} }
} }
} }
Ok(()) Ok(())
} }
pub fn editorconfg_properties(
&self,
for_worktree: WorktreeId,
for_path: &Path,
) -> Option<EditorconfigProperties> {
let mut properties = EditorconfigProperties::new();
for (directory_with_config, _, parsed_editorconfig) in
self.local_editorconfig_settings(for_worktree)
{
if !for_path.starts_with(&directory_with_config) {
properties.use_fallbacks();
return Some(properties);
}
let parsed_editorconfig = parsed_editorconfig?;
if parsed_editorconfig.is_root {
properties = EditorconfigProperties::new();
}
for section in parsed_editorconfig.sections {
section.apply_to(&mut properties, for_path).log_err()?;
}
}
properties.use_fallbacks();
Some(properties)
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -1002,8 +890,6 @@ pub enum InvalidSettingsError {
UserSettings { message: String }, UserSettings { message: String },
ServerSettings { message: String }, ServerSettings { message: String },
DefaultSettings { message: String }, DefaultSettings { message: String },
Editorconfig { path: PathBuf, message: String },
Tasks { message: String },
} }
impl std::fmt::Display for InvalidSettingsError { impl std::fmt::Display for InvalidSettingsError {
@@ -1012,10 +898,8 @@ impl std::fmt::Display for InvalidSettingsError {
InvalidSettingsError::LocalSettings { message, .. } InvalidSettingsError::LocalSettings { message, .. }
| InvalidSettingsError::UserSettings { message } | InvalidSettingsError::UserSettings { message }
| InvalidSettingsError::ServerSettings { message } | InvalidSettingsError::ServerSettings { message }
| InvalidSettingsError::DefaultSettings { message } | InvalidSettingsError::DefaultSettings { message } => {
| InvalidSettingsError::Tasks { message } write!(f, "{}", message)
| InvalidSettingsError::Editorconfig { message, .. } => {
write!(f, "{message}")
} }
} }
} }

View File

@@ -121,7 +121,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
let file = buffer.file(); let file = buffer.file();
let language = buffer.language_at(cursor_position); let language = buffer.language_at(cursor_position);
let settings = all_language_settings(file, cx); let settings = all_language_settings(file, cx);
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx) settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
} }
fn refresh( fn refresh(

View File

@@ -17,8 +17,6 @@ gpui.workspace = true
settings.workspace = true settings.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
workspace.workspace = true
menu.workspace = true
[features] [features]
default = [] default = []

View File

@@ -1,5 +1,3 @@
#![allow(unused, dead_code)]
//! # UI Text Field //! # UI Text Field
//! //!
//! This crate provides a text field component that can be used to create text fields like search inputs, form fields, etc. //! This crate provides a text field component that can be used to create text fields like search inputs, form fields, etc.
@@ -7,14 +5,11 @@
//! It can't be located in the `ui` crate because it depends on `editor`. //! It can't be located in the `ui` crate because it depends on `editor`.
//! //!
use std::default;
use editor::*; use editor::*;
use gpui::*; use gpui::*;
use settings::Settings; use settings::Settings;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{List, *}; use ui::*;
use workspace::{ModalView, Workspace};
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum FieldLabelLayout { pub enum FieldLabelLayout {
@@ -192,243 +187,3 @@ impl Render for TextField {
) )
} }
} }
// -------------------------------------------------------------------------------------------------
actions!(quick_commit, [ToggleStageAll]);
pub const MODAL_WIDTH: f32 = 700.0;
pub const MODAL_HEIGHT: f32 = 300.0;
fn test_files() -> Vec<ChangedFile> {
vec![
ChangedFile {
id: 0,
state: FileVCSState::Modified,
file_name: "file1.txt".into(),
file_path: "/path/to/file1.txt".into(),
},
ChangedFile {
id: 1,
state: FileVCSState::Deleted,
file_name: "file2.txt".into(),
file_path: "/path/to/file2.txt".into(),
},
ChangedFile {
id: 2,
state: FileVCSState::Created,
file_name: "file3.txt".into(),
file_path: "/path/to/file3.txt".into(),
},
]
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
enum FileVCSState {
Deleted,
Modified,
Created,
}
struct ChangedFileId(usize);
impl ChangedFileId {
fn new(id: usize) -> Self {
Self(id)
}
}
// placeholder for ui
#[derive(Debug, Clone)]
struct ChangedFile {
id: usize,
state: FileVCSState,
file_name: SharedString,
file_path: SharedString,
}
struct QuickCommitState {
placeholder_text: SharedString,
tracked_files: Vec<ChangedFile>,
staged_files: Vec<usize>,
active_participant_handles: Vec<SharedString>,
editor: View<Editor>,
workspace: WeakView<Workspace>,
}
impl QuickCommitState {
fn init(
editor: View<Editor>,
workspace: WeakView<Workspace>,
cx: &mut ModelContext<Self>,
) -> Self {
let workspace = workspace.clone();
Self {
placeholder_text: "Add a message".into(),
tracked_files: Default::default(),
staged_files: Default::default(),
active_participant_handles: Default::default(),
editor,
workspace,
}
}
fn stage_state(&self) -> Selection {
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
if staged_files.len() == tracked_files.len() {
Selection::Selected
} else if staged_files.is_empty() {
Selection::Unselected
} else {
Selection::Indeterminate
}
}
fn stage_all(&mut self) -> &mut Self {
let tracked_files = self.tracked_files.clone();
self.staged_files = tracked_files.iter().map(|file| file.id).collect();
self
}
fn toggle_stage_all(&mut self) {
let stage_state = self.stage_state();
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
match stage_state {
Selection::Selected => {
self.staged_files.clear();
}
Selection::Unselected | Selection::Indeterminate => {
self.stage_all();
}
}
}
fn toggle_file_staged(&mut self, file_id: usize) {
if let Some(pos) = self.staged_files.iter().position() {
self.staged_files.swap_remove(pos);
} else {
self.staged_files.push(file_id);
}
}
}
pub struct QuickCommit {
state: Model<QuickCommitState>,
}
impl QuickCommit {
pub fn init(workspace: WeakView<Workspace>, cx: &mut WindowContext) -> View<Self> {
let editor = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.set_show_gutter(false, cx);
editor
});
cx.new_view(|cx| {
let state = cx
.new_model(move |cx| QuickCommitState::init(editor.clone(), workspace.clone(), cx));
Self { state }
})
}
fn stage_state(&self, cx: &ViewContext<Self>) -> Selection {
self.state.read(cx).stage_state()
}
fn toggle_stage_all(&mut self, _: &ToggleStageAll, cx: &mut ViewContext<Self>) {
self.state.update(cx, |state, _| state.toggle_stage_all());
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent)
}
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.state.read(cx).editor.focus_handle(cx)
}
}
impl QuickCommit {
fn render_file_list(&mut self, cx: &mut ViewContext<Self>) -> List {
List::new().empty_message("No changes")
}
}
impl Render for QuickCommit {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let staged_files = self.state.read(cx).staged_files.clone();
let total_tracked_files = self.state.read(cx).tracked_files.clone();
let staged_state = self.stage_state(cx);
h_flex()
.id("quick_commit_modal")
.key_context("quick_commit")
.track_focus(&self.focus_handle(cx))
.on_action(cx.listener(Self::cancel))
.occlude()
.h(px(MODAL_HEIGHT))
.w(px(MODAL_WIDTH))
.child(
// commit editor
div()
.h_full()
.flex_1()
// .child(self.editor.clone())
.child(
div()
.absolute()
.bottom_2()
.right_2()
.child(Button::new("submit_commit", "Commit")),
),
)
.child(
// file list
div()
.w(relative(0.42))
.h_full()
.border_l_1()
.border_color(cx.theme().colors().border)
// sticky header
.child(
h_flex()
.h_10()
.w_full()
.child(Label::new(format!(
"Staged Files: {}/{}",
staged_files.len(),
total_tracked_files.len()
)))
.child(Checkbox::new("toggle-stage-all", staged_state).on_click(
|_, cx| {
cx.dispatch_action(ToggleStageAll.boxed_clone());
},
)),
)
// file list
.child(self.render_file_list(cx)),
)
}
}
impl EventEmitter<DismissEvent> for QuickCommit {}
impl FocusableView for QuickCommit {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
// TODO: Not sure this is right
self.focus_handle(cx)
}
}
impl ModalView for QuickCommit {
fn fade_out_background(&self) -> bool {
true
}
}

View File

@@ -98,40 +98,32 @@ impl Vim {
} }
} }
fn increment_decimal_string(num: &str, delta: i64) -> String { fn increment_decimal_string(mut num: &str, mut delta: i64) -> String {
let (negative, delta, num_str) = match num.strip_prefix('-') { let mut negative = false;
Some(n) => (true, -delta, n), if num.chars().next() == Some('-') {
None => (false, delta, num), negative = true;
}; delta = 0 - delta;
let num_length = num_str.len(); num = &num[1..];
let leading_zero = num_str.starts_with('0'); }
let result = if let Ok(value) = u64::from_str_radix(num, 10) {
let (result, new_negative) = match u64::from_str_radix(num_str, 10) { let wrapped = value.wrapping_add_signed(delta);
Ok(value) => { if delta < 0 && wrapped > value {
let wrapped = value.wrapping_add_signed(delta); negative = !negative;
if delta < 0 && wrapped > value { (u64::MAX - wrapped).wrapping_add(1)
((u64::MAX - wrapped).wrapping_add(1), !negative) } else if delta > 0 && wrapped < value {
} else if delta > 0 && wrapped < value { negative = !negative;
(u64::MAX - wrapped, !negative) u64::MAX - wrapped
} else { } else {
(wrapped, negative) wrapped
}
} }
Err(_) => (u64::MAX, negative), } else {
u64::MAX
}; };
let formatted = format!("{}", result); if result == 0 || !negative {
let new_significant_digits = formatted.len(); format!("{}", result)
let padding = if leading_zero {
num_length.saturating_sub(new_significant_digits)
} else { } else {
0 format!("-{}", result)
};
if new_negative && result != 0 {
format!("-{}{}", "0".repeat(padding), formatted)
} else {
format!("{}{}", "0".repeat(padding), formatted)
} }
} }
@@ -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] #[gpui::test]
async fn test_increment_with_two_dots(cx: &mut gpui::TestAppContext) { async fn test_increment_with_two_dots(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await; 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] #[gpui::test]
async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) { async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await; let mut cx = NeovimBackedTestContext::new(cx).await;

View File

@@ -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"}}

View File

@@ -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"}}

View File

@@ -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"}}

View File

@@ -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"}}

View File

@@ -1,4 +0,0 @@
{"Put":{"state":"001ˇ0\n"}}
{"Key":"10"}
{"Key":"ctrl-x"}
{"Get":{"state":"000ˇ9\n","mode":"Normal"}}

View File

@@ -1739,9 +1739,11 @@ impl Pane {
.worktree_for_entry(entry, cx)? .worktree_for_entry(entry, cx)?
.read(cx); .read(cx);
let entry = worktree.entry_for_id(entry)?; let entry = worktree.entry_for_id(entry)?;
match &entry.canonical_path { let abs_path = worktree.absolutize(&entry.path).ok()?;
Some(canonical_path) => Some(canonical_path.to_path_buf()), if entry.is_symlink {
None => worktree.absolutize(&entry.path).ok(), abs_path.canonicalize().ok()
} else {
Some(abs_path)
} }
} }

View File

@@ -3203,6 +3203,7 @@ pub struct Entry {
pub mtime: Option<SystemTime>, pub mtime: Option<SystemTime>,
pub canonical_path: Option<Box<Path>>, pub canonical_path: Option<Box<Path>>,
pub is_symlink: bool,
/// Whether this entry is ignored by Git. /// Whether this entry is ignored by Git.
/// ///
/// We only scan ignored entries once the directory is expanded and /// We only scan ignored entries once the directory is expanded and
@@ -3279,6 +3280,7 @@ impl Entry {
mtime: Some(metadata.mtime), mtime: Some(metadata.mtime),
size: metadata.len, size: metadata.len,
canonical_path, canonical_path,
is_symlink: metadata.is_symlink,
is_ignored: false, is_ignored: false,
is_external: false, is_external: false,
is_private: false, is_private: false,
@@ -5247,15 +5249,12 @@ impl<'a> From<&'a Entry> for proto::Entry {
path: entry.path.to_string_lossy().into(), path: entry.path.to_string_lossy().into(),
inode: entry.inode, inode: entry.inode,
mtime: entry.mtime.map(|time| time.into()), mtime: entry.mtime.map(|time| time.into()),
is_symlink: entry.is_symlink,
is_ignored: entry.is_ignored, is_ignored: entry.is_ignored,
is_external: entry.is_external, is_external: entry.is_external,
git_status: entry.git_status.map(git_status_to_proto), git_status: entry.git_status.map(git_status_to_proto),
is_fifo: entry.is_fifo, is_fifo: entry.is_fifo,
size: Some(entry.size), size: Some(entry.size),
canonical_path: entry
.canonical_path
.as_ref()
.map(|path| path.to_string_lossy().to_string()),
} }
} }
} }
@@ -5278,13 +5277,12 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
inode: entry.inode, inode: entry.inode,
mtime: entry.mtime.map(|time| time.into()), mtime: entry.mtime.map(|time| time.into()),
size: entry.size.unwrap_or(0), size: entry.size.unwrap_or(0),
canonical_path: entry canonical_path: None,
.canonical_path
.map(|path_string| Box::from(Path::new(&path_string))),
is_ignored: entry.is_ignored, is_ignored: entry.is_ignored,
is_external: entry.is_external, is_external: entry.is_external,
git_status: git_status_from_proto(entry.git_status), git_status: git_status_from_proto(entry.git_status),
is_private: false, is_private: false,
is_symlink: entry.is_symlink,
char_bag, char_bag,
is_fifo: entry.is_fifo, is_fifo: entry.is_fifo,
}) })

View File

@@ -32,7 +32,6 @@ Zed supports hundreds of programming languages and text formats. Some work out-o
- [JavaScript](./languages/javascript.md) - [JavaScript](./languages/javascript.md)
- [Julia](./languages/julia.md) - [Julia](./languages/julia.md)
- [JSON](./languages/json.md) - [JSON](./languages/json.md)
- [Jsonnet](./languages/jsonnet.md)
- [Kotlin](./languages/kotlin.md) - [Kotlin](./languages/kotlin.md)
- [Lua](./languages/lua.md) - [Lua](./languages/lua.md)
- [Luau](./languages/luau.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) - [Groq](https://github.com/juice49/zed-groq)
- [INI](https://github.com/bajrangCoder/zed-ini) - [INI](https://github.com/bajrangCoder/zed-ini)
- [Java](https://github.com/zed-extensions/java) - [Java](https://github.com/zed-extensions/java)
- [Jsonnet](https://github.com/narqo/zed-jsonnet)
- [Justfiles](https://github.com/jackTabsCode/zed-just) - [Justfiles](https://github.com/jackTabsCode/zed-just)
- [LaTeX](https://github.com/rzukic/zed-latex) - [LaTeX](https://github.com/rzukic/zed-latex)
- [Ledger](https://github.com/mrkstwrt/zed-ledger) - [Ledger](https://github.com/mrkstwrt/zed-ledger)

View File

@@ -24,7 +24,7 @@ To use a binary in a custom location, add the following to your `settings.json`:
} }
``` ```
If you want to disable Zed looking for a `clangd` binary, you can set `ignore_system_version` to `true`: If you want to disable Zed looking for a `clangd` binary, you can set `ignore_system-version` to `true`:
```json ```json
{ {

View File

@@ -17,21 +17,7 @@ The `OmniSharp` binary can be configured in a Zed settings file with:
"omnisharp": { "omnisharp": {
"binary": { "binary": {
"path": "/path/to/OmniSharp", "path": "/path/to/OmniSharp",
"arguments": ["optional", "additional", "args", "-lsp"] "args": ["optional", "additional", "args", "-lsp"]
}
}
}
}
```
If you want to disable Zed looking for a `omnisharp` binary, you can set `ignore_system_version` to `true`:
```json
{
"lsp": {
"omnisharp": {
"binary": {
"ignore_system_version": true
} }
} }
} }

View File

@@ -10,7 +10,9 @@ Both use:
- Tree Sitter: [tree-sitter/tree-sitter-java](https://github.com/tree-sitter/tree-sitter-java) - 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) - 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). 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/). 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 ## Extension Install
You can install either by opening {#action zed::Extensions}({#kb zed::Extensions}) and searching for `java`. 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. We recommend you install one or the other and not both.
## Settings / Initialization Options ## 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). Add the following to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}).
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.
### Zed Java Settings ### Zed Java Settings
@@ -41,10 +54,7 @@ You can add these customizations to your Zed Settings by launching {#action zed:
{ {
"lsp": { "lsp": {
"jdtls": { "jdtls": {
"settings": { "settings": {},
"version": "1.40.0", // jdtls version to download and use
"classpath": "/path/to/classes.jar:/path/to/more/classes/"
},
"initialization_options": {} "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 ## 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 ### Zed Java Initialization Options
There are also many more options you can pass directly to the language server, for example: 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)

View File

@@ -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
}
}
}
}
```

View File

@@ -1,6 +1,6 @@
# Vue # Vue
Vue support is available through the [Vue extension](https://github.com/zed-extensions/vue). Vue support is available through the [Vue extension](https://github.com/zed-industries/zed/tree/main/extensions/vue).
- Tree Sitter: [tree-sitter-grammars/tree-sitter-vue](https://github.com/tree-sitter-grammars/tree-sitter-vue) - Tree Sitter: [tree-sitter-grammars/tree-sitter-vue](https://github.com/tree-sitter-grammars/tree-sitter-vue)
- Language Server: [vuejs/language-tools/](https://github.com/vuejs/language-tools/) - Language Server: [vuejs/language-tools/](https://github.com/vuejs/language-tools/)

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "zed_elixir" name = "zed_elixir"
version = "0.1.1" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false publish = false
license = "Apache-2.0" license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "elixir" id = "elixir"
name = "Elixir" name = "Elixir"
description = "Elixir support." description = "Elixir support."
version = "0.1.1" version = "0.1.0"
schema_version = 1 schema_version = 1
authors = ["Marshall Bowers <elliott.codes@gmail.com>"] authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
repository = "https://github.com/zed-industries/zed" repository = "https://github.com/zed-industries/zed"

View File

@@ -3,16 +3,6 @@
(arguments (alias) @name) (arguments (alias) @name)
(#match? @context "^(defmodule|defprotocol)$")) @item (#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 (unary_operator
operator: "@" @name operator: "@" @name
operand: (call operand: (call

3
extensions/svelte/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
target
*.wasm
grammars

View 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"

View File

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

View 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"

View File

@@ -0,0 +1,7 @@
("<" @open ">" @close)
("{" @open "}" @close)
("'" @open "'" @close)
("\"" @open "\"" @close)
("(" @open ")" @close)
; ("[" @open "]" @close)
; ("`" @open "`" @close)

View 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"]

View 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

View File

@@ -0,0 +1,9 @@
[
(element)
(if_statement)
(each_statement)
(await_statement)
(snippet_statement)
(script_element)
(style_element)
] @indent

View 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

View 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

Some files were not shown because too many files have changed in this diff Show More