mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
Merge branch 'main' into storybook
This commit is contained in:
commit
15ea4af5e7
22 changed files with 734 additions and 96 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -8425,6 +8425,15 @@ dependencies = [
|
|||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-nu"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/nushell/tree-sitter-nu?rev=786689b0562b9799ce53e824cb45a1a2a04dc673#786689b0562b9799ce53e824cb45a1a2a04dc673"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-php"
|
||||
version = "0.19.1"
|
||||
|
@ -9887,6 +9896,7 @@ dependencies = [
|
|||
"tree-sitter-lua",
|
||||
"tree-sitter-markdown",
|
||||
"tree-sitter-nix",
|
||||
"tree-sitter-nu",
|
||||
"tree-sitter-php",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-racket",
|
||||
|
|
|
@ -142,6 +142,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack
|
|||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
|
||||
tree-sitter-lua = "0.0.14"
|
||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }
|
||||
|
|
37
README.md
37
README.md
|
@ -8,7 +8,31 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
|
|||
|
||||
### Dependencies
|
||||
|
||||
* Install [Postgres.app](https://postgresapp.com) and start it.
|
||||
* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license:
|
||||
```
|
||||
sudo xcodebuild -license
|
||||
```
|
||||
|
||||
* Install homebrew, rust and node
|
||||
```
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew install rust
|
||||
brew install node
|
||||
```
|
||||
|
||||
* Ensure rust executables are in your $PATH
|
||||
```
|
||||
echo $HOME/.cargo/bin | sudo tee /etc/paths.d/10-rust
|
||||
```
|
||||
|
||||
* Install postgres and configure the database
|
||||
```
|
||||
brew install postgresql@15
|
||||
brew services start postgresql@15
|
||||
psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres
|
||||
psql -U postgres -c "CREATE DATABASE zed"
|
||||
```
|
||||
|
||||
* Install the `LiveKit` server and the `foreman` process supervisor:
|
||||
|
||||
```
|
||||
|
@ -41,6 +65,17 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
|
|||
GITHUB_TOKEN=<$token> script/bootstrap
|
||||
```
|
||||
|
||||
* Now try running zed with collaboration disabled:
|
||||
```
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Common errors
|
||||
|
||||
* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH`
|
||||
* You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer`
|
||||
* (see https://github.com/gfx-rs/gfx/issues/2309)
|
||||
|
||||
### Testing against locally-running servers
|
||||
|
||||
Start the web and collab servers:
|
||||
|
|
|
@ -198,6 +198,18 @@
|
|||
"z c": "editor::Fold",
|
||||
"z o": "editor::UnfoldLines",
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"shift-z shift-q": [
|
||||
"pane::CloseActiveItem",
|
||||
{
|
||||
"saveBehavior": "dontSave"
|
||||
}
|
||||
],
|
||||
"shift-z shift-z": [
|
||||
"pane::CloseActiveItem",
|
||||
{
|
||||
"saveBehavior": "promptOnConflict"
|
||||
}
|
||||
],
|
||||
// Count support
|
||||
"1": [
|
||||
"vim::Number",
|
||||
|
|
|
@ -1528,8 +1528,13 @@ mod tests {
|
|||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||
active_pane
|
||||
.update(cx, |pane, cx| {
|
||||
pane.close_active_item(&workspace::CloseActiveItem, cx)
|
||||
.unwrap()
|
||||
pane.close_active_item(
|
||||
&workspace::CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
@ -42,8 +42,8 @@
|
|||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
|
||||
"version": "1.21.0"
|
||||
"revision": "ce20dc083ee485524b802669890291c0d8090170",
|
||||
"version": "1.22.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -34,6 +34,7 @@ use std::{
|
|||
ops::{Not, Range},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
|
@ -130,6 +131,7 @@ pub struct ProjectSearchView {
|
|||
|
||||
struct SemanticState {
|
||||
index_status: SemanticIndexStatus,
|
||||
maintain_rate_limit: Option<Task<()>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
|
@ -319,11 +321,28 @@ impl View for ProjectSearchView {
|
|||
let status = semantic.index_status;
|
||||
match status {
|
||||
SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()),
|
||||
SemanticIndexStatus::Indexing { remaining_files } => {
|
||||
SemanticIndexStatus::Indexing {
|
||||
remaining_files,
|
||||
rate_limit_expiry,
|
||||
} => {
|
||||
if remaining_files == 0 {
|
||||
Some(format!("Indexing..."))
|
||||
} else {
|
||||
Some(format!("Remaining files to index: {}", remaining_files))
|
||||
if let Some(rate_limit_expiry) = rate_limit_expiry {
|
||||
let remaining_seconds =
|
||||
rate_limit_expiry.duration_since(Instant::now());
|
||||
if remaining_seconds > Duration::from_secs(0) {
|
||||
Some(format!(
|
||||
"Remaining files to index (rate limit resets in {}s): {}",
|
||||
remaining_seconds.as_secs(),
|
||||
remaining_files
|
||||
))
|
||||
} else {
|
||||
Some(format!("Remaining files to index: {}", remaining_files))
|
||||
}
|
||||
} else {
|
||||
Some(format!("Remaining files to index: {}", remaining_files))
|
||||
}
|
||||
}
|
||||
}
|
||||
SemanticIndexStatus::NotIndexed => None,
|
||||
|
@ -651,9 +670,10 @@ impl ProjectSearchView {
|
|||
|
||||
self.semantic_state = Some(SemanticState {
|
||||
index_status: semantic_index.read(cx).status(&project),
|
||||
maintain_rate_limit: None,
|
||||
_subscription: cx.observe(&semantic_index, Self::semantic_index_changed),
|
||||
});
|
||||
cx.notify();
|
||||
self.semantic_index_changed(semantic_index, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,8 +684,25 @@ impl ProjectSearchView {
|
|||
) {
|
||||
let project = self.model.read(cx).project.clone();
|
||||
if let Some(semantic_state) = self.semantic_state.as_mut() {
|
||||
semantic_state.index_status = semantic_index.read(cx).status(&project);
|
||||
cx.notify();
|
||||
semantic_state.index_status = semantic_index.read(cx).status(&project);
|
||||
if let SemanticIndexStatus::Indexing {
|
||||
rate_limit_expiry: Some(_),
|
||||
..
|
||||
} = &semantic_state.index_status
|
||||
{
|
||||
if semantic_state.maintain_rate_limit.is_none() {
|
||||
semantic_state.maintain_rate_limit =
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
loop {
|
||||
cx.background().timer(Duration::from_secs(1)).await;
|
||||
this.update(&mut cx, |_, cx| cx.notify()).log_err();
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
semantic_state.maintain_rate_limit = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,16 @@ use isahc::http::StatusCode;
|
|||
use isahc::prelude::Configurable;
|
||||
use isahc::{AsyncBody, Response};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use parse_duration::parse;
|
||||
use postage::watch;
|
||||
use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
|
||||
use rusqlite::ToSql;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::ops::Add;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use tiktoken_rs::{cl100k_base, CoreBPE};
|
||||
use util::http::{HttpClient, Request};
|
||||
|
||||
|
@ -82,6 +85,8 @@ impl ToSql for Embedding {
|
|||
pub struct OpenAIEmbeddings {
|
||||
pub client: Arc<dyn HttpClient>,
|
||||
pub executor: Arc<Background>,
|
||||
rate_limit_count_rx: watch::Receiver<Option<Instant>>,
|
||||
rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -114,12 +119,16 @@ pub trait EmbeddingProvider: Sync + Send {
|
|||
async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>>;
|
||||
fn max_tokens_per_batch(&self) -> usize;
|
||||
fn truncate(&self, span: &str) -> (String, usize);
|
||||
fn rate_limit_expiration(&self) -> Option<Instant>;
|
||||
}
|
||||
|
||||
pub struct DummyEmbeddings {}
|
||||
|
||||
#[async_trait]
|
||||
impl EmbeddingProvider for DummyEmbeddings {
|
||||
fn rate_limit_expiration(&self) -> Option<Instant> {
|
||||
None
|
||||
}
|
||||
async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
|
||||
// 1024 is the OpenAI Embeddings size for ada models.
|
||||
// the model we will likely be starting with.
|
||||
|
@ -149,6 +158,50 @@ impl EmbeddingProvider for DummyEmbeddings {
|
|||
const OPENAI_INPUT_LIMIT: usize = 8190;
|
||||
|
||||
impl OpenAIEmbeddings {
|
||||
pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Background>) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
OpenAIEmbeddings {
|
||||
client,
|
||||
executor,
|
||||
rate_limit_count_rx,
|
||||
rate_limit_count_tx,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_rate_limit(&self) {
|
||||
let reset_time = *self.rate_limit_count_tx.lock().borrow();
|
||||
|
||||
if let Some(reset_time) = reset_time {
|
||||
if Instant::now() >= reset_time {
|
||||
*self.rate_limit_count_tx.lock().borrow_mut() = None
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"resolving reset time: {:?}",
|
||||
*self.rate_limit_count_tx.lock().borrow()
|
||||
);
|
||||
}
|
||||
|
||||
fn update_reset_time(&self, reset_time: Instant) {
|
||||
let original_time = *self.rate_limit_count_tx.lock().borrow();
|
||||
|
||||
let updated_time = if let Some(original_time) = original_time {
|
||||
if reset_time < original_time {
|
||||
Some(reset_time)
|
||||
} else {
|
||||
Some(original_time)
|
||||
}
|
||||
} else {
|
||||
Some(reset_time)
|
||||
};
|
||||
|
||||
log::trace!("updating rate limit time: {:?}", updated_time);
|
||||
|
||||
*self.rate_limit_count_tx.lock().borrow_mut() = updated_time;
|
||||
}
|
||||
async fn send_request(
|
||||
&self,
|
||||
api_key: &str,
|
||||
|
@ -179,6 +232,9 @@ impl EmbeddingProvider for OpenAIEmbeddings {
|
|||
50000
|
||||
}
|
||||
|
||||
fn rate_limit_expiration(&self) -> Option<Instant> {
|
||||
*self.rate_limit_count_rx.borrow()
|
||||
}
|
||||
fn truncate(&self, span: &str) -> (String, usize) {
|
||||
let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span);
|
||||
let output = if tokens.len() > OPENAI_INPUT_LIMIT {
|
||||
|
@ -203,6 +259,7 @@ impl EmbeddingProvider for OpenAIEmbeddings {
|
|||
.ok_or_else(|| anyhow!("no api key"))?;
|
||||
|
||||
let mut request_number = 0;
|
||||
let mut rate_limiting = false;
|
||||
let mut request_timeout: u64 = 15;
|
||||
let mut response: Response<AsyncBody>;
|
||||
while request_number < MAX_RETRIES {
|
||||
|
@ -229,6 +286,12 @@ impl EmbeddingProvider for OpenAIEmbeddings {
|
|||
response.usage.total_tokens
|
||||
);
|
||||
|
||||
// If we complete a request successfully that was previously rate_limited
|
||||
// resolve the rate limit
|
||||
if rate_limiting {
|
||||
self.resolve_rate_limit()
|
||||
}
|
||||
|
||||
return Ok(response
|
||||
.data
|
||||
.into_iter()
|
||||
|
@ -236,6 +299,7 @@ impl EmbeddingProvider for OpenAIEmbeddings {
|
|||
.collect());
|
||||
}
|
||||
StatusCode::TOO_MANY_REQUESTS => {
|
||||
rate_limiting = true;
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
||||
|
@ -254,6 +318,10 @@ impl EmbeddingProvider for OpenAIEmbeddings {
|
|||
}
|
||||
};
|
||||
|
||||
// If we've previously rate limited, increment the duration but not the count
|
||||
let reset_time = Instant::now().add(delay_duration);
|
||||
self.update_reset_time(reset_time);
|
||||
|
||||
log::trace!(
|
||||
"openai rate limiting: waiting {:?} until lifted",
|
||||
&delay_duration
|
||||
|
|
|
@ -91,10 +91,7 @@ pub fn init(
|
|||
let semantic_index = SemanticIndex::new(
|
||||
fs,
|
||||
db_file_path,
|
||||
Arc::new(OpenAIEmbeddings {
|
||||
client: http_client,
|
||||
executor: cx.background(),
|
||||
}),
|
||||
Arc::new(OpenAIEmbeddings::new(http_client, cx.background())),
|
||||
language_registry,
|
||||
cx.clone(),
|
||||
)
|
||||
|
@ -113,7 +110,10 @@ pub fn init(
|
|||
pub enum SemanticIndexStatus {
|
||||
NotIndexed,
|
||||
Indexed,
|
||||
Indexing { remaining_files: usize },
|
||||
Indexing {
|
||||
remaining_files: usize,
|
||||
rate_limit_expiry: Option<Instant>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct SemanticIndex {
|
||||
|
@ -293,6 +293,7 @@ impl SemanticIndex {
|
|||
} else {
|
||||
SemanticIndexStatus::Indexing {
|
||||
remaining_files: project_state.pending_file_count_rx.borrow().clone(),
|
||||
rate_limit_expiry: self.embedding_provider.rate_limit_expiration(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -21,7 +21,7 @@ use std::{
|
|||
atomic::{self, AtomicUsize},
|
||||
Arc,
|
||||
},
|
||||
time::SystemTime,
|
||||
time::{Instant, SystemTime},
|
||||
};
|
||||
use unindent::Unindent;
|
||||
use util::RandomCharIter;
|
||||
|
@ -1275,6 +1275,10 @@ impl EmbeddingProvider for FakeEmbeddingProvider {
|
|||
200
|
||||
}
|
||||
|
||||
fn rate_limit_expiration(&self) -> Option<Instant> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
|
||||
self.embedding_count
|
||||
.fetch_add(spans.len(), atomic::Ordering::SeqCst);
|
||||
|
|
|
@ -283,7 +283,12 @@ impl TerminalView {
|
|||
pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||
let menu_entries = vec![
|
||||
ContextMenuItem::action("Clear", Clear),
|
||||
ContextMenuItem::action("Close", pane::CloseActiveItem),
|
||||
ContextMenuItem::action(
|
||||
"Close",
|
||||
pane::CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
self.context_menu.update(cx, |menu, cx| {
|
||||
|
|
|
@ -474,8 +474,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
for item_event in T::to_item_events(event).into_iter() {
|
||||
match item_event {
|
||||
ItemEvent::CloseItem => {
|
||||
pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx))
|
||||
.detach_and_log_err(cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(
|
||||
item.id(),
|
||||
crate::SaveBehavior::PromptOnWrite,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,19 @@ use std::{
|
|||
};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SaveBehavior {
|
||||
/// ask before overwriting conflicting files (used by default with %s)
|
||||
PromptOnConflict,
|
||||
/// ask before writing any file that wouldn't be auto-saved (used by default with %w)
|
||||
PromptOnWrite,
|
||||
/// never prompt, write on conflict (used with vim's :w!)
|
||||
SilentlyOverwrite,
|
||||
/// skip all save-related behaviour (used with vim's :cq)
|
||||
DontSave,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct ActivateItem(pub usize);
|
||||
|
||||
|
@ -64,13 +77,17 @@ pub struct CloseItemsToTheRightById {
|
|||
pub pane: WeakViewHandle<Pane>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
pub struct CloseActiveItem {
|
||||
pub save_behavior: Option<SaveBehavior>,
|
||||
}
|
||||
|
||||
actions!(
|
||||
pane,
|
||||
[
|
||||
ActivatePrevItem,
|
||||
ActivateNextItem,
|
||||
ActivateLastItem,
|
||||
CloseActiveItem,
|
||||
CloseInactiveItems,
|
||||
CloseCleanItems,
|
||||
CloseItemsToTheLeft,
|
||||
|
@ -86,7 +103,7 @@ actions!(
|
|||
]
|
||||
);
|
||||
|
||||
impl_actions!(pane, [ActivateItem]);
|
||||
impl_actions!(pane, [ActivateItem, CloseActiveItem]);
|
||||
|
||||
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||
|
||||
|
@ -696,22 +713,29 @@ impl Pane {
|
|||
|
||||
pub fn close_active_item(
|
||||
&mut self,
|
||||
_: &CloseActiveItem,
|
||||
action: &CloseActiveItem,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].id();
|
||||
Some(self.close_item_by_id(active_item_id, cx))
|
||||
Some(self.close_item_by_id(
|
||||
active_item_id,
|
||||
action.save_behavior.unwrap_or(SaveBehavior::PromptOnWrite),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn close_item_by_id(
|
||||
&mut self,
|
||||
item_id_to_close: usize,
|
||||
save_behavior: SaveBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.close_items(cx, move |view_id| view_id == item_id_to_close)
|
||||
self.close_items(cx, save_behavior, move |view_id| {
|
||||
view_id == item_id_to_close
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_inactive_items(
|
||||
|
@ -724,7 +748,11 @@ impl Pane {
|
|||
}
|
||||
|
||||
let active_item_id = self.items[self.active_item_index].id();
|
||||
Some(self.close_items(cx, move |item_id| item_id != active_item_id))
|
||||
Some(
|
||||
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
|
||||
item_id != active_item_id
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn close_clean_items(
|
||||
|
@ -737,7 +765,11 @@ impl Pane {
|
|||
.filter(|item| !item.is_dirty(cx))
|
||||
.map(|item| item.id())
|
||||
.collect();
|
||||
Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id)))
|
||||
Some(
|
||||
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
|
||||
item_ids.contains(&item_id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_left(
|
||||
|
@ -762,7 +794,9 @@ impl Pane {
|
|||
.take_while(|item| item.id() != item_id)
|
||||
.map(|item| item.id())
|
||||
.collect();
|
||||
self.close_items(cx, move |item_id| item_ids.contains(&item_id))
|
||||
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
|
||||
item_ids.contains(&item_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_right(
|
||||
|
@ -788,7 +822,9 @@ impl Pane {
|
|||
.take_while(|item| item.id() != item_id)
|
||||
.map(|item| item.id())
|
||||
.collect();
|
||||
self.close_items(cx, move |item_id| item_ids.contains(&item_id))
|
||||
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
|
||||
item_ids.contains(&item_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_all_items(
|
||||
|
@ -800,12 +836,13 @@ impl Pane {
|
|||
return None;
|
||||
}
|
||||
|
||||
Some(self.close_items(cx, move |_| true))
|
||||
Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true))
|
||||
}
|
||||
|
||||
pub fn close_items(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
save_behavior: SaveBehavior,
|
||||
should_close: impl 'static + Fn(usize) -> bool,
|
||||
) -> Task<Result<()>> {
|
||||
// Find the items to close.
|
||||
|
@ -858,8 +895,15 @@ impl Pane {
|
|||
.any(|id| saved_project_items_ids.insert(*id));
|
||||
|
||||
if should_save
|
||||
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
|
||||
.await?
|
||||
&& !Self::save_item(
|
||||
project.clone(),
|
||||
&pane,
|
||||
item_ix,
|
||||
&*item,
|
||||
save_behavior,
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -954,13 +998,17 @@ impl Pane {
|
|||
pane: &WeakViewHandle<Pane>,
|
||||
item_ix: usize,
|
||||
item: &dyn ItemHandle,
|
||||
should_prompt_for_save: bool,
|
||||
save_behavior: SaveBehavior,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<bool> {
|
||||
const CONFLICT_MESSAGE: &str =
|
||||
"This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
||||
const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?";
|
||||
|
||||
if save_behavior == SaveBehavior::DontSave {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| {
|
||||
(
|
||||
item.has_conflict(cx),
|
||||
|
@ -971,18 +1019,22 @@ impl Pane {
|
|||
});
|
||||
|
||||
if has_conflict && can_save {
|
||||
let mut answer = pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
CONFLICT_MESSAGE,
|
||||
&["Overwrite", "Discard", "Cancel"],
|
||||
)
|
||||
})?;
|
||||
match answer.next().await {
|
||||
Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
|
||||
Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
|
||||
_ => return Ok(false),
|
||||
if save_behavior == SaveBehavior::SilentlyOverwrite {
|
||||
pane.update(cx, |_, cx| item.save(project, cx))?.await?;
|
||||
} else {
|
||||
let mut answer = pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
CONFLICT_MESSAGE,
|
||||
&["Overwrite", "Discard", "Cancel"],
|
||||
)
|
||||
})?;
|
||||
match answer.next().await {
|
||||
Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
|
||||
Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
|
||||
_ => return Ok(false),
|
||||
}
|
||||
}
|
||||
} else if is_dirty && (can_save || is_singleton) {
|
||||
let will_autosave = cx.read(|cx| {
|
||||
|
@ -991,7 +1043,7 @@ impl Pane {
|
|||
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||
) && Self::can_autosave_item(&*item, cx)
|
||||
});
|
||||
let should_save = if should_prompt_for_save && !will_autosave {
|
||||
let should_save = if save_behavior == SaveBehavior::PromptOnWrite && !will_autosave {
|
||||
let mut answer = pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
|
@ -1113,7 +1165,12 @@ impl Pane {
|
|||
AnchorCorner::TopLeft,
|
||||
if is_active_item {
|
||||
vec![
|
||||
ContextMenuItem::action("Close Active Item", CloseActiveItem),
|
||||
ContextMenuItem::action(
|
||||
"Close Active Item",
|
||||
CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
),
|
||||
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
|
||||
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
|
||||
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
|
||||
|
@ -1128,8 +1185,12 @@ impl Pane {
|
|||
move |cx| {
|
||||
if let Some(pane) = pane.upgrade(cx) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(target_item_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
pane.close_item_by_id(
|
||||
target_item_id,
|
||||
SaveBehavior::PromptOnWrite,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1278,7 +1339,12 @@ impl Pane {
|
|||
.on_click(MouseButton::Middle, {
|
||||
let item_id = item.id();
|
||||
move |_, pane, cx| {
|
||||
pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
|
||||
pane.close_item_by_id(
|
||||
item_id,
|
||||
SaveBehavior::PromptOnWrite,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.on_down(
|
||||
|
@ -1486,7 +1552,8 @@ impl Pane {
|
|||
cx.window_context().defer(move |cx| {
|
||||
if let Some(pane) = pane.upgrade(cx) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
|
||||
pane.close_item_by_id(item_id, SaveBehavior::PromptOnWrite, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -2087,7 +2154,14 @@ mod tests {
|
|||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert!(pane.close_active_item(&CloseActiveItem, cx).is_none())
|
||||
assert!(pane
|
||||
.close_active_item(
|
||||
&CloseActiveItem {
|
||||
save_behavior: None
|
||||
},
|
||||
cx
|
||||
)
|
||||
.is_none())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2337,31 +2411,59 @@ mod tests {
|
|||
add_labeled_item(&pane, "1", false, cx);
|
||||
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_active_item(
|
||||
&CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
|
||||
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_active_item(
|
||||
&CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A", "B*", "C"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_active_item(
|
||||
&CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A", "C*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_active_item(
|
||||
&CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A*"], cx);
|
||||
}
|
||||
|
||||
|
|
|
@ -1308,13 +1308,15 @@ impl Workspace {
|
|||
}
|
||||
|
||||
Ok(this
|
||||
.update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.save_all_internal(SaveBehavior::PromptOnWrite, cx)
|
||||
})?
|
||||
.await?)
|
||||
})
|
||||
}
|
||||
|
||||
fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||
let save_all = self.save_all_internal(false, cx);
|
||||
let save_all = self.save_all_internal(SaveBehavior::PromptOnConflict, cx);
|
||||
Some(cx.foreground().spawn(async move {
|
||||
save_all.await?;
|
||||
Ok(())
|
||||
|
@ -1323,7 +1325,7 @@ impl Workspace {
|
|||
|
||||
fn save_all_internal(
|
||||
&mut self,
|
||||
should_prompt_to_save: bool,
|
||||
save_behaviour: SaveBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<bool>> {
|
||||
if self.project.read(cx).is_read_only() {
|
||||
|
@ -1358,7 +1360,7 @@ impl Workspace {
|
|||
&pane,
|
||||
ix,
|
||||
&*item,
|
||||
should_prompt_to_save,
|
||||
save_behaviour,
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
|
@ -4358,7 +4360,9 @@ mod tests {
|
|||
let item1_id = item1.id();
|
||||
let item3_id = item3.id();
|
||||
let item4_id = item4.id();
|
||||
pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
|
||||
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| {
|
||||
[item1_id, item3_id, item4_id].contains(&id)
|
||||
})
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
|
@ -4493,7 +4497,9 @@ mod tests {
|
|||
// once for project entry 0, and once for project entry 2. After those two
|
||||
// prompts, the task should complete.
|
||||
|
||||
let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
|
||||
let close = left_pane.update(cx, |pane, cx| {
|
||||
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true)
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
left_pane.read_with(cx, |pane, cx| {
|
||||
assert_eq!(
|
||||
|
@ -4609,9 +4615,11 @@ mod tests {
|
|||
item.is_dirty = true;
|
||||
});
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!window.has_pending_prompt(cx));
|
||||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||
|
||||
|
@ -4630,8 +4638,9 @@ mod tests {
|
|||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||
|
||||
// Ensure autosave is prevented for deleted files also when closing the buffer.
|
||||
let _close_items =
|
||||
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
|
||||
let _close_items = pane.update(cx, |pane, cx| {
|
||||
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
assert!(window.has_pending_prompt(cx));
|
||||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||
|
|
|
@ -132,6 +132,7 @@ tree-sitter-racket.workspace = true
|
|||
tree-sitter-yaml.workspace = true
|
||||
tree-sitter-lua.workspace = true
|
||||
tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
|
|
|
@ -170,6 +170,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<dyn NodeRuntime>
|
|||
language("elm", tree_sitter_elm::language(), vec![]);
|
||||
language("glsl", tree_sitter_glsl::language(), vec![]);
|
||||
language("nix", tree_sitter_nix::language(), vec![]);
|
||||
language("nu", tree_sitter_nu::language(), vec![]);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
|
4
crates/zed/src/languages/nu/brackets.scm
Normal file
4
crates/zed/src/languages/nu/brackets.scm
Normal file
|
@ -0,0 +1,4 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
(parameter_pipes "|" @open "|" @close)
|
9
crates/zed/src/languages/nu/config.toml
Normal file
9
crates/zed/src/languages/nu/config.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name = "Nu"
|
||||
path_suffixes = ["nu"]
|
||||
line_comment = "# "
|
||||
autoclose_before = ";:.,=}])>` \n\t\""
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
]
|
302
crates/zed/src/languages/nu/highlights.scm
Normal file
302
crates/zed/src/languages/nu/highlights.scm
Normal file
|
@ -0,0 +1,302 @@
|
|||
;;; ---
|
||||
;;; keywords
|
||||
[
|
||||
"def"
|
||||
"def-env"
|
||||
"alias"
|
||||
"export-env"
|
||||
"export"
|
||||
"extern"
|
||||
"module"
|
||||
|
||||
"let"
|
||||
"let-env"
|
||||
"mut"
|
||||
"const"
|
||||
|
||||
"hide-env"
|
||||
|
||||
"source"
|
||||
"source-env"
|
||||
|
||||
"overlay"
|
||||
"register"
|
||||
|
||||
"loop"
|
||||
"while"
|
||||
"error"
|
||||
|
||||
"do"
|
||||
"if"
|
||||
"else"
|
||||
"try"
|
||||
"catch"
|
||||
"match"
|
||||
|
||||
"break"
|
||||
"continue"
|
||||
"return"
|
||||
|
||||
] @keyword
|
||||
|
||||
(hide_mod "hide" @keyword)
|
||||
(decl_use "use" @keyword)
|
||||
|
||||
(ctrl_for
|
||||
"for" @keyword
|
||||
"in" @keyword
|
||||
)
|
||||
(overlay_list "list" @keyword)
|
||||
(overlay_hide "hide" @keyword)
|
||||
(overlay_new "new" @keyword)
|
||||
(overlay_use
|
||||
"use" @keyword
|
||||
"as" @keyword
|
||||
)
|
||||
(ctrl_error "make" @keyword)
|
||||
|
||||
;;; ---
|
||||
;;; literals
|
||||
(val_number) @constant
|
||||
(val_duration
|
||||
unit: [
|
||||
"ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk"
|
||||
] @variable
|
||||
)
|
||||
(val_filesize
|
||||
unit: [
|
||||
"b" "B"
|
||||
|
||||
"kb" "kB" "Kb" "KB"
|
||||
"mb" "mB" "Mb" "MB"
|
||||
"gb" "gB" "Gb" "GB"
|
||||
"tb" "tB" "Tb" "TB"
|
||||
"pb" "pB" "Pb" "PB"
|
||||
"eb" "eB" "Eb" "EB"
|
||||
"zb" "zB" "Zb" "ZB"
|
||||
|
||||
"kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
|
||||
"mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
|
||||
"gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB"
|
||||
"tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
|
||||
"pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
|
||||
"eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
|
||||
"zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
|
||||
] @variable
|
||||
)
|
||||
(val_binary
|
||||
[
|
||||
"0b"
|
||||
"0o"
|
||||
"0x"
|
||||
] @constant
|
||||
"[" @punctuation.bracket
|
||||
digit: [
|
||||
"," @punctuation.delimiter
|
||||
(hex_digit) @constant
|
||||
]
|
||||
"]" @punctuation.bracket
|
||||
) @constant
|
||||
(val_bool) @constant.builtin
|
||||
(val_nothing) @constant.builtin
|
||||
(val_string) @string
|
||||
(val_date) @constant
|
||||
(inter_escape_sequence) @constant
|
||||
(escape_sequence) @constant
|
||||
(val_interpolated [
|
||||
"$\""
|
||||
"$\'"
|
||||
"\""
|
||||
"\'"
|
||||
] @string)
|
||||
(unescaped_interpolated_content) @string
|
||||
(escaped_interpolated_content) @string
|
||||
(expr_interpolated ["(" ")"] @variable)
|
||||
|
||||
;;; ---
|
||||
;;; operators
|
||||
(expr_binary [
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"mod"
|
||||
"//"
|
||||
"++"
|
||||
"**"
|
||||
"=="
|
||||
"!="
|
||||
"<"
|
||||
"<="
|
||||
">"
|
||||
">="
|
||||
"=~"
|
||||
"!~"
|
||||
"and"
|
||||
"or"
|
||||
"xor"
|
||||
"bit-or"
|
||||
"bit-xor"
|
||||
"bit-and"
|
||||
"bit-shl"
|
||||
"bit-shr"
|
||||
"in"
|
||||
"not-in"
|
||||
"starts-with"
|
||||
"ends-with"
|
||||
] @operator)
|
||||
|
||||
(expr_binary opr: ([
|
||||
"and"
|
||||
"or"
|
||||
"xor"
|
||||
"bit-or"
|
||||
"bit-xor"
|
||||
"bit-and"
|
||||
"bit-shl"
|
||||
"bit-shr"
|
||||
"in"
|
||||
"not-in"
|
||||
"starts-with"
|
||||
"ends-with"
|
||||
]) @keyword)
|
||||
|
||||
(where_command [
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"mod"
|
||||
"//"
|
||||
"++"
|
||||
"**"
|
||||
"=="
|
||||
"!="
|
||||
"<"
|
||||
"<="
|
||||
">"
|
||||
">="
|
||||
"=~"
|
||||
"!~"
|
||||
"and"
|
||||
"or"
|
||||
"xor"
|
||||
"bit-or"
|
||||
"bit-xor"
|
||||
"bit-and"
|
||||
"bit-shl"
|
||||
"bit-shr"
|
||||
"in"
|
||||
"not-in"
|
||||
"starts-with"
|
||||
"ends-with"
|
||||
] @operator)
|
||||
|
||||
(assignment [
|
||||
"="
|
||||
"+="
|
||||
"-="
|
||||
"*="
|
||||
"/="
|
||||
"++="
|
||||
] @operator)
|
||||
|
||||
(expr_unary ["not" "-"] @operator)
|
||||
|
||||
(val_range [
|
||||
".."
|
||||
"..="
|
||||
"..<"
|
||||
] @operator)
|
||||
|
||||
["=>" "=" "|"] @operator
|
||||
|
||||
[
|
||||
"o>" "out>"
|
||||
"e>" "err>"
|
||||
"e+o>" "err+out>"
|
||||
"o+e>" "out+err>"
|
||||
] @special
|
||||
|
||||
;;; ---
|
||||
;;; punctuation
|
||||
[
|
||||
","
|
||||
";"
|
||||
] @punctuation.delimiter
|
||||
|
||||
(param_short_flag "-" @punctuation.delimiter)
|
||||
(param_long_flag ["--"] @punctuation.delimiter)
|
||||
(long_flag ["--"] @punctuation.delimiter)
|
||||
(param_rest "..." @punctuation.delimiter)
|
||||
(param_type [":"] @punctuation.special)
|
||||
(param_value ["="] @punctuation.special)
|
||||
(param_cmd ["@"] @punctuation.special)
|
||||
(param_opt ["?"] @punctuation.special)
|
||||
|
||||
[
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
] @punctuation.bracket
|
||||
|
||||
(val_record
|
||||
(record_entry ":" @punctuation.delimiter))
|
||||
;;; ---
|
||||
;;; identifiers
|
||||
(param_rest
|
||||
name: (_) @variable)
|
||||
(param_opt
|
||||
name: (_) @variable)
|
||||
(parameter
|
||||
param_name: (_) @variable)
|
||||
(param_cmd
|
||||
(cmd_identifier) @string)
|
||||
(param_long_flag) @variable
|
||||
(param_short_flag) @variable
|
||||
|
||||
(short_flag) @variable
|
||||
(long_flag) @variable
|
||||
|
||||
(scope_pattern [(wild_card) @function])
|
||||
|
||||
(cmd_identifier) @function
|
||||
|
||||
(command
|
||||
"^" @punctuation.delimiter
|
||||
head: (_) @function
|
||||
)
|
||||
|
||||
"where" @function
|
||||
|
||||
(path
|
||||
["." "?"] @punctuation.delimiter
|
||||
) @variable
|
||||
|
||||
(val_variable
|
||||
"$" @operator
|
||||
[
|
||||
(identifier) @variable
|
||||
"in" @type.builtin
|
||||
"nu" @type.builtin
|
||||
"env" @type.builtin
|
||||
"nothing" @type.builtin
|
||||
] ; If we have a special styling, use it here
|
||||
)
|
||||
;;; ---
|
||||
;;; types
|
||||
(flat_type) @type.builtin
|
||||
(list_type
|
||||
"list" @type
|
||||
["<" ">"] @punctuation.bracket
|
||||
)
|
||||
(collection_type
|
||||
["record" "table"] @type
|
||||
"<" @punctuation.bracket
|
||||
key: (_) @variable
|
||||
["," ":"] @punctuation.delimiter
|
||||
">" @punctuation.bracket
|
||||
)
|
||||
|
||||
(shebang) @comment
|
||||
(comment) @comment
|
3
crates/zed/src/languages/nu/indents.scm
Normal file
3
crates/zed/src/languages/nu/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
|
@ -41,7 +41,12 @@ pub fn menus() -> Vec<Menu<'static>> {
|
|||
MenuItem::action("Save", workspace::Save),
|
||||
MenuItem::action("Save As…", workspace::SaveAs),
|
||||
MenuItem::action("Save All", workspace::SaveAll),
|
||||
MenuItem::action("Close Editor", workspace::CloseActiveItem),
|
||||
MenuItem::action(
|
||||
"Close Editor",
|
||||
workspace::CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
),
|
||||
MenuItem::action("Close Window", workspace::CloseWindow),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -733,7 +733,7 @@ mod tests {
|
|||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use workspace::{
|
||||
item::{Item, ItemHandle},
|
||||
open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle,
|
||||
open_new, open_paths, pane, NewFile, SaveBehavior, SplitDirection, WorkspaceHandle,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1495,7 +1495,12 @@ mod tests {
|
|||
|
||||
pane2_item.downcast::<Editor>().unwrap().downgrade()
|
||||
});
|
||||
cx.dispatch_action(window.into(), workspace::CloseActiveItem);
|
||||
cx.dispatch_action(
|
||||
window.into(),
|
||||
workspace::CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
);
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
workspace.read_with(cx, |workspace, _| {
|
||||
|
@ -1503,7 +1508,12 @@ mod tests {
|
|||
assert_eq!(workspace.active_pane(), &pane_1);
|
||||
});
|
||||
|
||||
cx.dispatch_action(window.into(), workspace::CloseActiveItem);
|
||||
cx.dispatch_action(
|
||||
window.into(),
|
||||
workspace::CloseActiveItem {
|
||||
save_behavior: None,
|
||||
},
|
||||
);
|
||||
cx.foreground().run_until_parked();
|
||||
window.simulate_prompt_answer(1, cx);
|
||||
cx.foreground().run_until_parked();
|
||||
|
@ -1661,7 +1671,7 @@ mod tests {
|
|||
pane.update(cx, |pane, cx| {
|
||||
let editor3_id = editor3.id();
|
||||
drop(editor3);
|
||||
pane.close_item_by_id(editor3_id, cx)
|
||||
pane.close_item_by_id(editor3_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1696,7 +1706,7 @@ mod tests {
|
|||
pane.update(cx, |pane, cx| {
|
||||
let editor2_id = editor2.id();
|
||||
drop(editor2);
|
||||
pane.close_item_by_id(editor2_id, cx)
|
||||
pane.close_item_by_id(editor2_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1852,24 +1862,32 @@ mod tests {
|
|||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||
|
||||
// Close all the pane items in some arbitrary order.
|
||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(file1_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(file4_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(file2_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||
|
||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(file3_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), None);
|
||||
|
||||
// Reopen all the closed items, ensuring they are reopened in the same order
|
||||
|
|
Loading…
Reference in a new issue