Merge branch 'zed2' into zed2-workspace
This commit is contained in:
commit
00c92ae407
202 changed files with 9668 additions and 332 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -10983,7 +10983,7 @@ dependencies = [
|
||||||
"ctor",
|
"ctor",
|
||||||
"db2",
|
"db2",
|
||||||
"env_logger 0.9.3",
|
"env_logger 0.9.3",
|
||||||
"feature_flags",
|
"feature_flags2",
|
||||||
"fs2",
|
"fs2",
|
||||||
"fsevent",
|
"fsevent",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
|
@ -11000,7 +11000,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp2",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
|
@ -11053,6 +11053,7 @@ dependencies = [
|
||||||
"tree-sitter-svelte",
|
"tree-sitter-svelte",
|
||||||
"tree-sitter-toml",
|
"tree-sitter-toml",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
|
"tree-sitter-vue",
|
||||||
"tree-sitter-yaml",
|
"tree-sitter-yaml",
|
||||||
"unindent",
|
"unindent",
|
||||||
"url",
|
"url",
|
||||||
|
|
|
@ -67,8 +67,8 @@ impl ActiveCall {
|
||||||
incoming_call: watch::channel(),
|
incoming_call: watch::channel(),
|
||||||
|
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
client.add_request_handler(cx.weak_handle(), Self::handle_incoming_call),
|
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||||
client.add_message_handler(cx.weak_handle(), Self::handle_call_canceled),
|
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
||||||
],
|
],
|
||||||
client,
|
client,
|
||||||
user_store,
|
user_store,
|
||||||
|
|
|
@ -122,9 +122,9 @@ impl UserStore {
|
||||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
||||||
let rpc_subscriptions = vec![
|
let rpc_subscriptions = vec![
|
||||||
client.add_message_handler(cx.weak_handle(), Self::handle_update_contacts),
|
client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
|
||||||
client.add_message_handler(cx.weak_handle(), Self::handle_update_invite_info),
|
client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
|
||||||
client.add_message_handler(cx.weak_handle(), Self::handle_show_contacts),
|
client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
|
||||||
];
|
];
|
||||||
Self {
|
Self {
|
||||||
users: Default::default(),
|
users: Default::default(),
|
||||||
|
|
|
@ -7,8 +7,8 @@ use async_tar::Archive;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||||
use gpui2::{
|
use gpui2::{
|
||||||
AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Model, ModelContext, Task,
|
AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext,
|
||||||
WeakModel,
|
Task, WeakModel,
|
||||||
};
|
};
|
||||||
use language2::{
|
use language2::{
|
||||||
language_settings::{all_language_settings, language_settings},
|
language_settings::{all_language_settings, language_settings},
|
||||||
|
|
|
@ -673,6 +673,10 @@ impl AppContext {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
|
||||||
|
self.action_builders.keys().cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Move the global of the given type to the stack.
|
/// Move the global of the given type to the stack.
|
||||||
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
|
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
|
||||||
GlobalLease::new(
|
GlobalLease::new(
|
||||||
|
@ -776,7 +780,7 @@ impl Context for AppContext {
|
||||||
|
|
||||||
/// Update the entity referenced by the given model. The function is passed a mutable reference to the
|
/// Update the entity referenced by the given model. The function is passed a mutable reference to the
|
||||||
/// entity along with a `ModelContext` for the entity.
|
/// entity along with a `ModelContext` for the entity.
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: &Model<T>,
|
model: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
|
|
|
@ -29,14 +29,14 @@ impl Context for AsyncAppContext {
|
||||||
Ok(lock.build_model(build_model))
|
Ok(lock.build_model(build_model))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
) -> Self::Result<R> {
|
) -> Self::Result<R> {
|
||||||
let app = self.app.upgrade().context("app was released")?;
|
let app = self.app.upgrade().context("app was released")?;
|
||||||
let mut lock = app.lock(); // Need this to compile
|
let mut lock = app.lock(); // Need this to compile
|
||||||
Ok(lock.update_entity(handle, update))
|
Ok(lock.update_model(handle, update))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,13 +248,13 @@ impl Context for AsyncWindowContext {
|
||||||
.update_window(self.window, |cx| cx.build_model(build_model))
|
.update_window(self.window, |cx| cx.build_model(build_model))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
) -> Result<R> {
|
) -> Result<R> {
|
||||||
self.app
|
self.app
|
||||||
.update_window(self.window, |cx| cx.update_entity(handle, update))
|
.update_window(self.window, |cx| cx.update_model(handle, update))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{AnyBox, AppContext, Context};
|
use crate::{private::Sealed, AnyBox, AppContext, Context, Entity};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||||
|
@ -172,14 +172,14 @@ impl AnyModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn downcast<T: 'static>(&self) -> Option<Model<T>> {
|
pub fn downcast<T: 'static>(self) -> Result<Model<T>, AnyModel> {
|
||||||
if TypeId::of::<T>() == self.entity_type {
|
if TypeId::of::<T>() == self.entity_type {
|
||||||
Some(Model {
|
Ok(Model {
|
||||||
any_model: self.clone(),
|
any_model: self,
|
||||||
entity_type: PhantomData,
|
entity_type: PhantomData,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,6 +243,14 @@ impl PartialEq for AnyModel {
|
||||||
|
|
||||||
impl Eq for AnyModel {}
|
impl Eq for AnyModel {}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for AnyModel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("AnyModel")
|
||||||
|
.field("entity_id", &self.entity_id.as_u64())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct Model<T> {
|
pub struct Model<T> {
|
||||||
#[deref]
|
#[deref]
|
||||||
|
@ -253,6 +261,32 @@ pub struct Model<T> {
|
||||||
|
|
||||||
unsafe impl<T> Send for Model<T> {}
|
unsafe impl<T> Send for Model<T> {}
|
||||||
unsafe impl<T> Sync for Model<T> {}
|
unsafe impl<T> Sync for Model<T> {}
|
||||||
|
impl<T> Sealed for Model<T> {}
|
||||||
|
|
||||||
|
impl<T: 'static> Entity<T> for Model<T> {
|
||||||
|
type Weak = WeakModel<T>;
|
||||||
|
|
||||||
|
fn entity_id(&self) -> EntityId {
|
||||||
|
self.any_model.entity_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade(&self) -> Self::Weak {
|
||||||
|
WeakModel {
|
||||||
|
any_model: self.any_model.downgrade(),
|
||||||
|
entity_type: self.entity_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(Model {
|
||||||
|
any_model: weak.any_model.upgrade()?,
|
||||||
|
entity_type: weak.entity_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> Model<T> {
|
impl<T: 'static> Model<T> {
|
||||||
fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
|
fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
|
||||||
|
@ -265,11 +299,12 @@ impl<T: 'static> Model<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrade the this to a weak model reference
|
||||||
pub fn downgrade(&self) -> WeakModel<T> {
|
pub fn downgrade(&self) -> WeakModel<T> {
|
||||||
WeakModel {
|
// Delegate to the trait implementation to keep behavior in one place.
|
||||||
any_model: self.any_model.downgrade(),
|
// This method was included to improve method resolution in the presence of
|
||||||
entity_type: self.entity_type,
|
// the Model's deref
|
||||||
}
|
Entity::downgrade(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this into a dynamically typed model.
|
/// Convert this into a dynamically typed model.
|
||||||
|
@ -294,7 +329,7 @@ impl<T: 'static> Model<T> {
|
||||||
where
|
where
|
||||||
C: Context,
|
C: Context,
|
||||||
{
|
{
|
||||||
cx.update_entity(self, update)
|
cx.update_model(self, update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +369,7 @@ impl<T> Eq for Model<T> {}
|
||||||
|
|
||||||
impl<T> PartialEq<WeakModel<T>> for Model<T> {
|
impl<T> PartialEq<WeakModel<T>> for Model<T> {
|
||||||
fn eq(&self, other: &WeakModel<T>) -> bool {
|
fn eq(&self, other: &WeakModel<T>) -> bool {
|
||||||
self.entity_id() == other.entity_id()
|
self.any_model.entity_id() == other.entity_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,11 +450,10 @@ impl<T> Clone for WeakModel<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> WeakModel<T> {
|
impl<T: 'static> WeakModel<T> {
|
||||||
|
/// Upgrade this weak model reference into a strong model reference
|
||||||
pub fn upgrade(&self) -> Option<Model<T>> {
|
pub fn upgrade(&self) -> Option<Model<T>> {
|
||||||
Some(Model {
|
// Delegate to the trait implementation to keep behavior in one place.
|
||||||
any_model: self.any_model.upgrade()?,
|
Model::upgrade_from(self)
|
||||||
entity_type: self.entity_type,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the entity referenced by this model with the given function if
|
/// Update the entity referenced by this model with the given function if
|
||||||
|
@ -441,7 +475,7 @@ impl<T: 'static> WeakModel<T> {
|
||||||
crate::Flatten::flatten(
|
crate::Flatten::flatten(
|
||||||
self.upgrade()
|
self.upgrade()
|
||||||
.ok_or_else(|| anyhow!("entity release"))
|
.ok_or_else(|| anyhow!("entity release"))
|
||||||
.map(|this| cx.update_entity(&this, update)),
|
.map(|this| cx.update_model(&this, update)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,6 +496,6 @@ impl<T> Eq for WeakModel<T> {}
|
||||||
|
|
||||||
impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
||||||
fn eq(&self, other: &Model<T>) -> bool {
|
fn eq(&self, other: &Model<T>) -> bool {
|
||||||
self.entity_id() == other.entity_id()
|
self.entity_id() == other.any_model.entity_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, MainThread, Model,
|
AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread,
|
||||||
Reference, Subscription, Task, WeakModel,
|
Model, Reference, Subscription, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
@ -31,29 +31,32 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(&self) -> Model<T> {
|
pub fn handle(&self) -> Model<T> {
|
||||||
self.weak_handle()
|
self.weak_model()
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.expect("The entity must be alive if we have a model context")
|
.expect("The entity must be alive if we have a model context")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weak_handle(&self) -> WeakModel<T> {
|
pub fn weak_model(&self) -> WeakModel<T> {
|
||||||
self.model_state.clone()
|
self.model_state.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe<T2: 'static>(
|
pub fn observe<T2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T2>,
|
entity: &E,
|
||||||
mut on_notify: impl FnMut(&mut T, Model<T2>, &mut ModelContext<'_, T>) + Send + 'static,
|
mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
|
T2: 'static,
|
||||||
|
E: Entity<T2>,
|
||||||
{
|
{
|
||||||
let this = self.weak_handle();
|
let this = self.weak_model();
|
||||||
let handle = handle.downgrade();
|
let entity_id = entity.entity_id();
|
||||||
|
let handle = entity.downgrade();
|
||||||
self.app.observers.insert(
|
self.app.observers.insert(
|
||||||
handle.entity_id,
|
entity_id,
|
||||||
Box::new(move |cx| {
|
Box::new(move |cx| {
|
||||||
if let Some((this, handle)) = this.upgrade().zip(handle.upgrade()) {
|
if let Some((this, handle)) = this.upgrade().zip(E::upgrade_from(&handle)) {
|
||||||
this.update(cx, |this, cx| on_notify(this, handle, cx));
|
this.update(cx, |this, cx| on_notify(this, handle, cx));
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,21 +66,24 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe<E: 'static + EventEmitter>(
|
pub fn subscribe<T2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<E>,
|
entity: &E,
|
||||||
mut on_event: impl FnMut(&mut T, Model<E>, &E::Event, &mut ModelContext<'_, T>) + Send + 'static,
|
mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
|
T2: 'static + EventEmitter,
|
||||||
|
E: Entity<T2>,
|
||||||
{
|
{
|
||||||
let this = self.weak_handle();
|
let this = self.weak_model();
|
||||||
let handle = handle.downgrade();
|
let entity_id = entity.entity_id();
|
||||||
|
let entity = entity.downgrade();
|
||||||
self.app.event_listeners.insert(
|
self.app.event_listeners.insert(
|
||||||
handle.entity_id,
|
entity_id,
|
||||||
Box::new(move |event, cx| {
|
Box::new(move |event, cx| {
|
||||||
let event: &E::Event = event.downcast_ref().expect("invalid event type");
|
let event: &T2::Event = event.downcast_ref().expect("invalid event type");
|
||||||
if let Some((this, handle)) = this.upgrade().zip(handle.upgrade()) {
|
if let Some((this, handle)) = this.upgrade().zip(E::upgrade_from(&entity)) {
|
||||||
this.update(cx, |this, cx| on_event(this, handle, event, cx));
|
this.update(cx, |this, cx| on_event(this, handle, event, cx));
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,20 +109,27 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_release<E: 'static>(
|
pub fn observe_release<T2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<E>,
|
entity: &E,
|
||||||
mut on_release: impl FnMut(&mut T, &mut E, &mut ModelContext<'_, T>) + Send + 'static,
|
mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
T: Any + Send,
|
T: Any + Send,
|
||||||
|
T2: 'static,
|
||||||
|
E: Entity<T2>,
|
||||||
{
|
{
|
||||||
let this = self.weak_handle();
|
let entity_id = entity.entity_id();
|
||||||
self.app.observe_release(handle, move |entity, cx| {
|
let this = self.weak_model();
|
||||||
if let Some(this) = this.upgrade() {
|
self.app.release_listeners.insert(
|
||||||
this.update(cx, |this, cx| on_release(this, entity, cx));
|
entity_id,
|
||||||
}
|
Box::new(move |entity, cx| {
|
||||||
})
|
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||||
|
if let Some(this) = this.upgrade() {
|
||||||
|
this.update(cx, |this, cx| on_release(this, entity, cx));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_global<G: 'static>(
|
pub fn observe_global<G: 'static>(
|
||||||
|
@ -126,7 +139,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
where
|
where
|
||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
{
|
{
|
||||||
let handle = self.weak_handle();
|
let handle = self.weak_model();
|
||||||
self.global_observers.insert(
|
self.global_observers.insert(
|
||||||
TypeId::of::<G>(),
|
TypeId::of::<G>(),
|
||||||
Box::new(move |cx| handle.update(cx, |view, cx| f(view, cx)).is_ok()),
|
Box::new(move |cx| handle.update(cx, |view, cx| f(view, cx)).is_ok()),
|
||||||
|
@ -141,7 +154,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
Fut: 'static + Future<Output = ()> + Send,
|
Fut: 'static + Future<Output = ()> + Send,
|
||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
{
|
{
|
||||||
let handle = self.weak_handle();
|
let handle = self.weak_model();
|
||||||
self.app.quit_observers.insert(
|
self.app.quit_observers.insert(
|
||||||
(),
|
(),
|
||||||
Box::new(move |cx| {
|
Box::new(move |cx| {
|
||||||
|
@ -187,7 +200,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
Fut: Future<Output = R> + Send + 'static,
|
Fut: Future<Output = R> + Send + 'static,
|
||||||
R: Send + 'static,
|
R: Send + 'static,
|
||||||
{
|
{
|
||||||
let this = self.weak_handle();
|
let this = self.weak_model();
|
||||||
self.app.spawn(|cx| f(this, cx))
|
self.app.spawn(|cx| f(this, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +212,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||||
Fut: Future<Output = R> + 'static,
|
Fut: Future<Output = R> + 'static,
|
||||||
R: Send + 'static,
|
R: Send + 'static,
|
||||||
{
|
{
|
||||||
let this = self.weak_handle();
|
let this = self.weak_model();
|
||||||
self.app.spawn_on_main(|cx| f(this, cx))
|
self.app.spawn_on_main(|cx| f(this, cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,12 +244,12 @@ impl<'a, T> Context for ModelContext<'a, T> {
|
||||||
self.app.build_model(build_model)
|
self.app.build_model(build_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<U: 'static, R>(
|
fn update_model<U: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<U>,
|
handle: &Model<U>,
|
||||||
update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R,
|
update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
self.app.update_entity(handle, update)
|
self.app.update_model(handle, update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,13 @@ impl Context for TestAppContext {
|
||||||
lock.build_model(build_model)
|
lock.build_model(build_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
) -> Self::Result<R> {
|
) -> Self::Result<R> {
|
||||||
let mut lock = self.app.lock();
|
let mut lock = self.app.lock();
|
||||||
lock.update_entity(handle, update)
|
lock.update_model(handle, update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,12 @@ mod util;
|
||||||
mod view;
|
mod view;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
/// A mechanism for restricting implementations of a trait to only those in GPUI.
|
||||||
|
/// See: https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
pub use action::*;
|
pub use action::*;
|
||||||
pub use anyhow::Result;
|
pub use anyhow::Result;
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
|
@ -39,6 +45,7 @@ pub use image_cache::*;
|
||||||
pub use interactive::*;
|
pub use interactive::*;
|
||||||
pub use keymap::*;
|
pub use keymap::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
|
use private::Sealed;
|
||||||
pub use refineable::*;
|
pub use refineable::*;
|
||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
pub use serde;
|
pub use serde;
|
||||||
|
@ -80,7 +87,7 @@ pub trait Context {
|
||||||
where
|
where
|
||||||
T: 'static + Send;
|
T: 'static + Send;
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
|
@ -104,7 +111,7 @@ pub trait VisualContext: Context {
|
||||||
) -> Self::Result<R>;
|
) -> Self::Result<R>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EntityHandle<T> {
|
pub trait Entity<T>: Sealed {
|
||||||
type Weak: 'static + Send;
|
type Weak: 'static + Send;
|
||||||
|
|
||||||
fn entity_id(&self) -> EntityId;
|
fn entity_id(&self) -> EntityId;
|
||||||
|
@ -159,12 +166,12 @@ impl<C: Context> Context for MainThread<C> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
) -> Self::Result<R> {
|
) -> Self::Result<R> {
|
||||||
self.0.update_entity(handle, |entity, cx| {
|
self.0.update_model(handle, |entity, cx| {
|
||||||
let cx = unsafe {
|
let cx = unsafe {
|
||||||
mem::transmute::<
|
mem::transmute::<
|
||||||
&mut C::ModelContext<'_, T>,
|
&mut C::ModelContext<'_, T>,
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow, Bounds, Component,
|
private::Sealed, AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow,
|
||||||
Element, ElementId, EntityHandle, EntityId, Flatten, LayoutId, Model, Pixels, Size,
|
Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels,
|
||||||
ViewContext, VisualContext, WeakModel, WindowContext,
|
Size, ViewContext, VisualContext, WeakModel, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{
|
use std::{any::TypeId, marker::PhantomData, sync::Arc};
|
||||||
any::{Any, TypeId},
|
|
||||||
marker::PhantomData,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait Render: 'static + Sized {
|
pub trait Render: 'static + Sized {
|
||||||
type Element: Element<Self> + 'static + Send;
|
type Element: Element<Self> + 'static + Send;
|
||||||
|
@ -20,19 +16,42 @@ pub struct View<V> {
|
||||||
pub(crate) model: Model<V>,
|
pub(crate) model: Model<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V> Sealed for View<V> {}
|
||||||
|
|
||||||
impl<V: Render> View<V> {
|
impl<V: Render> View<V> {
|
||||||
pub fn into_any(self) -> AnyView {
|
pub fn into_any(self) -> AnyView {
|
||||||
AnyView(Arc::new(self))
|
AnyView(Arc::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> View<V> {
|
impl<V: 'static> Entity<V> for View<V> {
|
||||||
pub fn downgrade(&self) -> WeakView<V> {
|
type Weak = WeakView<V>;
|
||||||
|
|
||||||
|
fn entity_id(&self) -> EntityId {
|
||||||
|
self.model.entity_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade(&self) -> Self::Weak {
|
||||||
WeakView {
|
WeakView {
|
||||||
model: self.model.downgrade(),
|
model: self.model.downgrade(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let model = weak.model.upgrade()?;
|
||||||
|
Some(View { model })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> View<V> {
|
||||||
|
/// Convert this strong view reference into a weak view reference.
|
||||||
|
pub fn downgrade(&self) -> WeakView<V> {
|
||||||
|
Entity::downgrade(self)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update<C, R>(
|
pub fn update<C, R>(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut C,
|
cx: &mut C,
|
||||||
|
@ -109,33 +128,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> EntityHandle<T> for View<T> {
|
|
||||||
type Weak = WeakView<T>;
|
|
||||||
|
|
||||||
fn entity_id(&self) -> EntityId {
|
|
||||||
self.model.entity_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade(&self) -> Self::Weak {
|
|
||||||
self.downgrade()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
weak.upgrade()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WeakView<V> {
|
pub struct WeakView<V> {
|
||||||
pub(crate) model: WeakModel<V>,
|
pub(crate) model: WeakModel<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> WeakView<V> {
|
impl<V: 'static> WeakView<V> {
|
||||||
pub fn upgrade(&self) -> Option<View<V>> {
|
pub fn upgrade(&self) -> Option<View<V>> {
|
||||||
let model = self.model.upgrade()?;
|
Entity::upgrade_from(self)
|
||||||
Some(View { model })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update<C, R>(
|
pub fn update<C, R>(
|
||||||
|
@ -216,7 +215,7 @@ trait ViewObject: Send + Sync {
|
||||||
fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
|
fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
|
||||||
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
|
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
|
||||||
fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
|
fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> ViewObject for View<V>
|
impl<V> ViewObject for View<V>
|
||||||
|
@ -228,7 +227,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entity_id(&self) -> EntityId {
|
fn entity_id(&self) -> EntityId {
|
||||||
self.model.entity_id
|
Entity::entity_id(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn model(&self) -> AnyModel {
|
fn model(&self) -> AnyModel {
|
||||||
|
@ -236,7 +235,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
|
fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
|
||||||
cx.with_element_id(self.model.entity_id, |_global_id, cx| {
|
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||||
self.update(cx, |state, cx| {
|
self.update(cx, |state, cx| {
|
||||||
let mut any_element = Box::new(AnyElement::new(state.render(cx)));
|
let mut any_element = Box::new(AnyElement::new(state.render(cx)));
|
||||||
any_element.initialize(state, cx);
|
any_element.initialize(state, cx);
|
||||||
|
@ -246,7 +245,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
|
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
|
||||||
cx.with_element_id(self.model.entity_id, |_global_id, cx| {
|
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||||
self.update(cx, |state, cx| {
|
self.update(cx, |state, cx| {
|
||||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||||
element.layout(state, cx)
|
element.layout(state, cx)
|
||||||
|
@ -255,7 +254,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
|
fn paint(&self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
|
||||||
cx.with_element_id(self.model.entity_id, |_global_id, cx| {
|
cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| {
|
||||||
self.update(cx, |state, cx| {
|
self.update(cx, |state, cx| {
|
||||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||||
element.paint(state, cx);
|
element.paint(state, cx);
|
||||||
|
@ -263,8 +262,10 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self
|
f.debug_struct(&format!("AnyView<{}>", std::any::type_name::<V>()))
|
||||||
|
.field("entity_id", &ViewObject::entity_id(self).as_u64())
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,8 +273,12 @@ where
|
||||||
pub struct AnyView(Arc<dyn ViewObject>);
|
pub struct AnyView(Arc<dyn ViewObject>);
|
||||||
|
|
||||||
impl AnyView {
|
impl AnyView {
|
||||||
pub fn downcast<V: 'static + Send>(self) -> Option<View<V>> {
|
pub fn downcast<V: 'static>(self) -> Result<View<V>, AnyView> {
|
||||||
self.0.model().downcast().map(|model| View { model })
|
self.0
|
||||||
|
.model()
|
||||||
|
.downcast()
|
||||||
|
.map(|model| View { model })
|
||||||
|
.map_err(|_| self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn entity_type(&self) -> TypeId {
|
pub(crate) fn entity_type(&self) -> TypeId {
|
||||||
|
@ -336,6 +341,12 @@ impl Element<()> for AnyView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for AnyView {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.debug(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct EraseAnyViewState<ParentViewState> {
|
struct EraseAnyViewState<ParentViewState> {
|
||||||
view: AnyView,
|
view: AnyView,
|
||||||
parent_view_state_type: PhantomData<ParentViewState>,
|
parent_view_state_type: PhantomData<ParentViewState>,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
||||||
Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
|
Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
|
||||||
EntityHandle, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId,
|
Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
|
||||||
GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke,
|
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
|
||||||
LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite,
|
MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
|
||||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
|
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow,
|
||||||
PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
|
Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
|
||||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
|
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
|
||||||
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
|
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
|
||||||
WindowOptions, SUBPIXEL_VARIANTS,
|
WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
|
@ -376,23 +376,23 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
self.notify();
|
self.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe<E, H>(
|
pub fn subscribe<Emitter, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &H,
|
entity: &E,
|
||||||
mut on_event: impl FnMut(H, &E::Event, &mut WindowContext<'_, '_>) + Send + 'static,
|
mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_, '_>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
E: EventEmitter,
|
Emitter: EventEmitter,
|
||||||
H: EntityHandle<E>,
|
E: Entity<Emitter>,
|
||||||
{
|
{
|
||||||
let entity_id = handle.entity_id();
|
let entity_id = entity.entity_id();
|
||||||
let handle = handle.downgrade();
|
let entity = entity.downgrade();
|
||||||
let window_handle = self.window.handle;
|
let window_handle = self.window.handle;
|
||||||
self.app.event_listeners.insert(
|
self.app.event_listeners.insert(
|
||||||
entity_id,
|
entity_id,
|
||||||
Box::new(move |event, cx| {
|
Box::new(move |event, cx| {
|
||||||
cx.update_window(window_handle, |cx| {
|
cx.update_window(window_handle, |cx| {
|
||||||
if let Some(handle) = H::upgrade_from(&handle) {
|
if let Some(handle) = E::upgrade_from(&entity) {
|
||||||
let event = event.downcast_ref().expect("invalid event type");
|
let event = event.downcast_ref().expect("invalid event type");
|
||||||
on_event(handle, event, cx);
|
on_event(handle, event, cx);
|
||||||
true
|
true
|
||||||
|
@ -1280,7 +1280,7 @@ impl Context for WindowContext<'_, '_> {
|
||||||
self.entities.insert(slot, model)
|
self.entities.insert(slot, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: &Model<T>,
|
model: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
|
@ -1595,23 +1595,25 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
|
||||||
self.window_cx.on_next_frame(move |cx| view.update(cx, f));
|
self.window_cx.on_next_frame(move |cx| view.update(cx, f));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe<E>(
|
pub fn observe<V2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<E>,
|
entity: &E,
|
||||||
mut on_notify: impl FnMut(&mut V, Model<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
E: 'static,
|
V2: 'static,
|
||||||
V: Any + Send,
|
V: Any + Send,
|
||||||
|
E: Entity<V2>,
|
||||||
{
|
{
|
||||||
let view = self.view();
|
let view = self.view();
|
||||||
let handle = handle.downgrade();
|
let entity_id = entity.entity_id();
|
||||||
|
let entity = entity.downgrade();
|
||||||
let window_handle = self.window.handle;
|
let window_handle = self.window.handle;
|
||||||
self.app.observers.insert(
|
self.app.observers.insert(
|
||||||
handle.entity_id,
|
entity_id,
|
||||||
Box::new(move |cx| {
|
Box::new(move |cx| {
|
||||||
cx.update_window(window_handle, |cx| {
|
cx.update_window(window_handle, |cx| {
|
||||||
if let Some(handle) = handle.upgrade() {
|
if let Some(handle) = E::upgrade_from(&entity) {
|
||||||
view.update(cx, |this, cx| on_notify(this, handle, cx))
|
view.update(cx, |this, cx| on_notify(this, handle, cx))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1623,24 +1625,24 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe<E, H>(
|
pub fn subscribe<V2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &H,
|
entity: &E,
|
||||||
mut on_event: impl FnMut(&mut V, H, &E::Event, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
E: EventEmitter,
|
V2: EventEmitter,
|
||||||
H: EntityHandle<E>,
|
E: Entity<V2>,
|
||||||
{
|
{
|
||||||
let view = self.view();
|
let view = self.view();
|
||||||
let entity_id = handle.entity_id();
|
let entity_id = entity.entity_id();
|
||||||
let handle = handle.downgrade();
|
let handle = entity.downgrade();
|
||||||
let window_handle = self.window.handle;
|
let window_handle = self.window.handle;
|
||||||
self.app.event_listeners.insert(
|
self.app.event_listeners.insert(
|
||||||
entity_id,
|
entity_id,
|
||||||
Box::new(move |event, cx| {
|
Box::new(move |event, cx| {
|
||||||
cx.update_window(window_handle, |cx| {
|
cx.update_window(window_handle, |cx| {
|
||||||
if let Some(handle) = H::upgrade_from(&handle) {
|
if let Some(handle) = E::upgrade_from(&handle) {
|
||||||
let event = event.downcast_ref().expect("invalid event type");
|
let event = event.downcast_ref().expect("invalid event type");
|
||||||
view.update(cx, |this, cx| on_event(this, handle, event, cx))
|
view.update(cx, |this, cx| on_event(this, handle, event, cx))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|
@ -1668,18 +1670,21 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_release<T: 'static>(
|
pub fn observe_release<V2, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
entity: &E,
|
||||||
mut on_release: impl FnMut(&mut V, &mut T, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
V: Any + Send,
|
V: Any + Send,
|
||||||
|
V2: 'static,
|
||||||
|
E: Entity<V2>,
|
||||||
{
|
{
|
||||||
let view = self.view();
|
let view = self.view();
|
||||||
|
let entity_id = entity.entity_id();
|
||||||
let window_handle = self.window.handle;
|
let window_handle = self.window.handle;
|
||||||
self.app.release_listeners.insert(
|
self.app.release_listeners.insert(
|
||||||
handle.entity_id,
|
entity_id,
|
||||||
Box::new(move |entity, cx| {
|
Box::new(move |entity, cx| {
|
||||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||||
let _ = cx.update_window(window_handle, |cx| {
|
let _ = cx.update_window(window_handle, |cx| {
|
||||||
|
@ -1904,12 +1909,12 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
|
||||||
self.window_cx.build_model(build_model)
|
self.window_cx.build_model(build_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entity<T: 'static, R>(
|
fn update_model<T: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: &Model<T>,
|
model: &Model<T>,
|
||||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
self.window_cx.update_entity(model, update)
|
self.window_cx.update_model(model, update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -230,8 +230,8 @@ impl CachedLspAdapter {
|
||||||
self.adapter.label_for_symbol(name, kind, language).await
|
self.adapter.label_for_symbol(name, kind, language).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
pub fn prettier_plugins(&self) -> &[&'static str] {
|
||||||
self.adapter.enabled_formatters()
|
self.adapter.prettier_plugins()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,31 +340,8 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
fn prettier_plugins(&self) -> &[&'static str] {
|
||||||
Vec::new()
|
&[]
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum BundledFormatter {
|
|
||||||
Prettier {
|
|
||||||
// See https://prettier.io/docs/en/options.html#parser for a list of valid values.
|
|
||||||
// Usually, every language has a single parser (standard or plugin-provided), hence `Some("parser_name")` can be used.
|
|
||||||
// There can not be multiple parsers for a single language, in case of a conflict, we would attempt to select the one with most plugins.
|
|
||||||
//
|
|
||||||
// But exceptions like Tailwind CSS exist, which uses standard parsers for CSS/JS/HTML/etc. but require an extra plugin to be installed.
|
|
||||||
// For those cases, `None` will install the plugin but apply other, regular parser defined for the language, and this would not be a conflict.
|
|
||||||
parser_name: Option<&'static str>,
|
|
||||||
plugin_names: Vec<&'static str>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BundledFormatter {
|
|
||||||
pub fn prettier(parser_name: &'static str) -> Self {
|
|
||||||
Self::Prettier {
|
|
||||||
parser_name: Some(parser_name),
|
|
||||||
plugin_names: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,6 +379,8 @@ pub struct LanguageConfig {
|
||||||
pub overrides: HashMap<String, LanguageConfigOverride>,
|
pub overrides: HashMap<String, LanguageConfigOverride>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub word_characters: HashSet<char>,
|
pub word_characters: HashSet<char>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub prettier_parser_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -475,6 +454,7 @@ impl Default for LanguageConfig {
|
||||||
overrides: Default::default(),
|
overrides: Default::default(),
|
||||||
collapsed_placeholder: Default::default(),
|
collapsed_placeholder: Default::default(),
|
||||||
word_characters: Default::default(),
|
word_characters: Default::default(),
|
||||||
|
prettier_parser_name: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,7 +480,7 @@ pub struct FakeLspAdapter {
|
||||||
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp2::FakeLanguageServer)>>,
|
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp2::FakeLanguageServer)>>,
|
||||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||||
pub disk_based_diagnostics_sources: Vec<String>,
|
pub disk_based_diagnostics_sources: Vec<String>,
|
||||||
pub enabled_formatters: Vec<BundledFormatter>,
|
pub prettier_plugins: Vec<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -1604,6 +1584,10 @@ impl Language {
|
||||||
override_id: None,
|
override_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prettier_parser_name(&self) -> Option<&str> {
|
||||||
|
self.config.prettier_parser_name.as_deref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageScope {
|
impl LanguageScope {
|
||||||
|
@ -1766,7 +1750,7 @@ impl Default for FakeLspAdapter {
|
||||||
disk_based_diagnostics_progress_token: None,
|
disk_based_diagnostics_progress_token: None,
|
||||||
initialization_options: None,
|
initialization_options: None,
|
||||||
disk_based_diagnostics_sources: Vec::new(),
|
disk_based_diagnostics_sources: Vec::new(),
|
||||||
enabled_formatters: Vec::new(),
|
prettier_plugins: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1824,8 +1808,8 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
||||||
self.initialization_options.clone()
|
self.initialization_options.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
fn prettier_plugins(&self) -> &[&'static str] {
|
||||||
self.enabled_formatters.clone()
|
&self.prettier_plugins
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::HashMap;
|
||||||
use fs2::Fs;
|
use fs2::Fs;
|
||||||
use gpui2::{AsyncAppContext, Model};
|
use gpui2::{AsyncAppContext, Model};
|
||||||
use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
|
use language2::{language_settings::language_settings, Buffer, Diff};
|
||||||
use lsp2::{LanguageServer, LanguageServerId};
|
use lsp2::{LanguageServer, LanguageServerId};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -189,128 +189,134 @@ impl Prettier {
|
||||||
) -> anyhow::Result<Diff> {
|
) -> anyhow::Result<Diff> {
|
||||||
match self {
|
match self {
|
||||||
Self::Real(local) => {
|
Self::Real(local) => {
|
||||||
let params = buffer.update(cx, |buffer, cx| {
|
let params = buffer
|
||||||
let buffer_language = buffer.language();
|
.update(cx, |buffer, cx| {
|
||||||
let parsers_with_plugins = buffer_language
|
let buffer_language = buffer.language();
|
||||||
.into_iter()
|
let parser_with_plugins = buffer_language.and_then(|l| {
|
||||||
.flat_map(|language| {
|
let prettier_parser = l.prettier_parser_name()?;
|
||||||
language
|
let mut prettier_plugins = l
|
||||||
.lsp_adapters()
|
.lsp_adapters()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|adapter| adapter.enabled_formatters())
|
.flat_map(|adapter| adapter.prettier_plugins())
|
||||||
.filter_map(|formatter| match formatter {
|
.collect::<Vec<_>>();
|
||||||
BundledFormatter::Prettier {
|
prettier_plugins.dedup();
|
||||||
parser_name,
|
Some((prettier_parser, prettier_plugins))
|
||||||
plugin_names,
|
});
|
||||||
} => Some((parser_name, plugin_names)),
|
|
||||||
})
|
let prettier_node_modules = self.prettier_dir().join("node_modules");
|
||||||
})
|
anyhow::ensure!(
|
||||||
.fold(
|
prettier_node_modules.is_dir(),
|
||||||
HashMap::default(),
|
"Prettier node_modules dir does not exist: {prettier_node_modules:?}"
|
||||||
|mut parsers_with_plugins, (parser_name, plugins)| {
|
);
|
||||||
match parser_name {
|
let plugin_name_into_path = |plugin_name: &str| {
|
||||||
Some(parser_name) => parsers_with_plugins
|
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
|
||||||
.entry(parser_name)
|
for possible_plugin_path in [
|
||||||
.or_insert_with(HashSet::default)
|
prettier_plugin_dir.join("dist").join("index.mjs"),
|
||||||
.extend(plugins),
|
prettier_plugin_dir.join("dist").join("index.js"),
|
||||||
None => parsers_with_plugins.values_mut().for_each(|existing_plugins| {
|
prettier_plugin_dir.join("dist").join("plugin.js"),
|
||||||
existing_plugins.extend(plugins.iter());
|
prettier_plugin_dir.join("index.mjs"),
|
||||||
}),
|
prettier_plugin_dir.join("index.js"),
|
||||||
|
prettier_plugin_dir.join("plugin.js"),
|
||||||
|
prettier_plugin_dir,
|
||||||
|
] {
|
||||||
|
if possible_plugin_path.is_file() {
|
||||||
|
return Some(possible_plugin_path);
|
||||||
}
|
}
|
||||||
parsers_with_plugins
|
}
|
||||||
},
|
None
|
||||||
|
};
|
||||||
|
let (parser, located_plugins) = match parser_with_plugins {
|
||||||
|
Some((parser, plugins)) => {
|
||||||
|
// Tailwind plugin requires being added last
|
||||||
|
// https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
|
||||||
|
let mut add_tailwind_back = false;
|
||||||
|
|
||||||
|
let mut plugins = plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|&&plugin_name| {
|
||||||
|
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
|
||||||
|
add_tailwind_back = true;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|plugin_name| {
|
||||||
|
(plugin_name, plugin_name_into_path(plugin_name))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if add_tailwind_back {
|
||||||
|
plugins.push((
|
||||||
|
&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
|
||||||
|
plugin_name_into_path(
|
||||||
|
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(Some(parser.to_string()), plugins)
|
||||||
|
}
|
||||||
|
None => (None, Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let prettier_options = if self.is_default() {
|
||||||
|
let language_settings =
|
||||||
|
language_settings(buffer_language, buffer.file(), cx);
|
||||||
|
let mut options = language_settings.prettier.clone();
|
||||||
|
if !options.contains_key("tabWidth") {
|
||||||
|
options.insert(
|
||||||
|
"tabWidth".to_string(),
|
||||||
|
serde_json::Value::Number(serde_json::Number::from(
|
||||||
|
language_settings.tab_size.get(),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !options.contains_key("printWidth") {
|
||||||
|
options.insert(
|
||||||
|
"printWidth".to_string(),
|
||||||
|
serde_json::Value::Number(serde_json::Number::from(
|
||||||
|
language_settings.preferred_line_length,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(options)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let plugins = located_plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(plugin_name, located_plugin_path)| {
|
||||||
|
match located_plugin_path {
|
||||||
|
Some(path) => Some(path),
|
||||||
|
None => {
|
||||||
|
log::error!(
|
||||||
|
"Have not found plugin path for {:?} inside {:?}",
|
||||||
|
plugin_name,
|
||||||
|
prettier_node_modules
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
log::debug!(
|
||||||
|
"Formatting file {:?} with prettier, plugins :{:?}, options: {:?}",
|
||||||
|
plugins,
|
||||||
|
prettier_options,
|
||||||
|
buffer.file().map(|f| f.full_path(cx))
|
||||||
);
|
);
|
||||||
|
|
||||||
let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len());
|
anyhow::Ok(FormatParams {
|
||||||
if parsers_with_plugins.len() > 1 {
|
text: buffer.text(),
|
||||||
log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}");
|
options: FormatOptions {
|
||||||
}
|
parser,
|
||||||
|
plugins,
|
||||||
let prettier_node_modules = self.prettier_dir().join("node_modules");
|
path: buffer_path,
|
||||||
anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}");
|
prettier_options,
|
||||||
let plugin_name_into_path = |plugin_name: &str| {
|
},
|
||||||
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
|
})
|
||||||
for possible_plugin_path in [
|
})?
|
||||||
prettier_plugin_dir.join("dist").join("index.mjs"),
|
.context("prettier params calculation")?;
|
||||||
prettier_plugin_dir.join("dist").join("index.js"),
|
|
||||||
prettier_plugin_dir.join("dist").join("plugin.js"),
|
|
||||||
prettier_plugin_dir.join("index.mjs"),
|
|
||||||
prettier_plugin_dir.join("index.js"),
|
|
||||||
prettier_plugin_dir.join("plugin.js"),
|
|
||||||
prettier_plugin_dir,
|
|
||||||
] {
|
|
||||||
if possible_plugin_path.is_file() {
|
|
||||||
return Some(possible_plugin_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let (parser, located_plugins) = match selected_parser_with_plugins {
|
|
||||||
Some((parser, plugins)) => {
|
|
||||||
// Tailwind plugin requires being added last
|
|
||||||
// https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
|
|
||||||
let mut add_tailwind_back = false;
|
|
||||||
|
|
||||||
let mut plugins = plugins.into_iter().filter(|&&plugin_name| {
|
|
||||||
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
|
|
||||||
add_tailwind_back = true;
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::<Vec<_>>();
|
|
||||||
if add_tailwind_back {
|
|
||||||
plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)));
|
|
||||||
}
|
|
||||||
(Some(parser.to_string()), plugins)
|
|
||||||
},
|
|
||||||
None => (None, Vec::new()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let prettier_options = if self.is_default() {
|
|
||||||
let language_settings = language_settings(buffer_language, buffer.file(), cx);
|
|
||||||
let mut options = language_settings.prettier.clone();
|
|
||||||
if !options.contains_key("tabWidth") {
|
|
||||||
options.insert(
|
|
||||||
"tabWidth".to_string(),
|
|
||||||
serde_json::Value::Number(serde_json::Number::from(
|
|
||||||
language_settings.tab_size.get(),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if !options.contains_key("printWidth") {
|
|
||||||
options.insert(
|
|
||||||
"printWidth".to_string(),
|
|
||||||
serde_json::Value::Number(serde_json::Number::from(
|
|
||||||
language_settings.preferred_line_length,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some(options)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| {
|
|
||||||
match located_plugin_path {
|
|
||||||
Some(path) => Some(path),
|
|
||||||
None => {
|
|
||||||
log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}");
|
|
||||||
None},
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx)));
|
|
||||||
|
|
||||||
anyhow::Ok(FormatParams {
|
|
||||||
text: buffer.text(),
|
|
||||||
options: FormatOptions {
|
|
||||||
parser,
|
|
||||||
plugins,
|
|
||||||
path: buffer_path,
|
|
||||||
prettier_options,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})?.context("prettier params calculation")?;
|
|
||||||
let response = local
|
let response = local
|
||||||
.server
|
.server
|
||||||
.request::<Format>(params)
|
.request::<Format>(params)
|
||||||
|
|
|
@ -26,8 +26,8 @@ use futures::{
|
||||||
};
|
};
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use gpui2::{
|
use gpui2::{
|
||||||
AnyModel, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext,
|
AnyModel, AppContext, AsyncAppContext, Context, Entity, EventEmitter, Executor, Model,
|
||||||
Task, WeakModel,
|
ModelContext, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language2::{
|
use language2::{
|
||||||
|
@ -39,11 +39,11 @@ use language2::{
|
||||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||||
serialize_anchor, serialize_version, split_operations,
|
serialize_anchor, serialize_version, split_operations,
|
||||||
},
|
},
|
||||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter,
|
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
|
||||||
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
|
||||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
|
||||||
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
|
OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
|
||||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use lsp2::{
|
use lsp2::{
|
||||||
|
@ -2491,7 +2491,7 @@ impl Project {
|
||||||
delay
|
delay
|
||||||
} else {
|
} else {
|
||||||
if first_insertion {
|
if first_insertion {
|
||||||
let this = cx.weak_handle();
|
let this = cx.weak_model();
|
||||||
cx.defer(move |cx| {
|
cx.defer(move |cx| {
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
@ -2986,8 +2986,8 @@ impl Project {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
move |mut params, mut cx| {
|
move |mut params, mut cx| {
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
adapter.process_diagnostics(&mut params);
|
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
|
adapter.process_diagnostics(&mut params);
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.update_diagnostics(
|
this.update_diagnostics(
|
||||||
server_id,
|
server_id,
|
||||||
|
@ -8410,12 +8410,7 @@ impl Project {
|
||||||
let Some(buffer_language) = buffer.language() else {
|
let Some(buffer_language) = buffer.language() else {
|
||||||
return Task::ready(None);
|
return Task::ready(None);
|
||||||
};
|
};
|
||||||
if !buffer_language
|
if buffer_language.prettier_parser_name().is_none() {
|
||||||
.lsp_adapters()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|adapter| adapter.enabled_formatters())
|
|
||||||
.any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. }))
|
|
||||||
{
|
|
||||||
return Task::ready(None);
|
return Task::ready(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8574,16 +8569,15 @@ impl Project {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut prettier_plugins = None;
|
let mut prettier_plugins = None;
|
||||||
for formatter in new_language
|
if new_language.prettier_parser_name().is_some() {
|
||||||
.lsp_adapters()
|
prettier_plugins
|
||||||
.into_iter()
|
.get_or_insert_with(|| HashSet::default())
|
||||||
.flat_map(|adapter| adapter.enabled_formatters())
|
.extend(
|
||||||
{
|
new_language
|
||||||
match formatter {
|
.lsp_adapters()
|
||||||
BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins
|
.iter()
|
||||||
.get_or_insert_with(|| HashSet::default())
|
.flat_map(|adapter| adapter.prettier_plugins()),
|
||||||
.extend(plugin_names),
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let Some(prettier_plugins) = prettier_plugins else {
|
let Some(prettier_plugins) = prettier_plugins else {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(()));
|
||||||
|
@ -8650,7 +8644,7 @@ fn subscribe_for_copilot_events(
|
||||||
// Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
|
// Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
|
||||||
if !copilot_server.has_notification_handler::<copilot2::request::LogMessage>() {
|
if !copilot_server.has_notification_handler::<copilot2::request::LogMessage>() {
|
||||||
let new_server_id = copilot_server.server_id();
|
let new_server_id = copilot_server.server_id();
|
||||||
let weak_project = cx.weak_handle();
|
let weak_project = cx.weak_model();
|
||||||
let copilot_log_subscription = copilot_server
|
let copilot_log_subscription = copilot_server
|
||||||
.on_notification::<copilot2::request::LogMessage, _>(
|
.on_notification::<copilot2::request::LogMessage, _>(
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::Project;
|
use crate::Project;
|
||||||
use gpui2::{AnyWindowHandle, Context, Model, ModelContext, WeakModel};
|
use gpui2::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
|
||||||
use settings2::Settings;
|
use settings2::Settings;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use terminal2::{
|
use terminal2::{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use gpui2::{AppContext, KeyBinding};
|
use gpui2::{AppContext, KeyBinding, SharedString};
|
||||||
use schemars::{
|
use schemars::{
|
||||||
gen::{SchemaGenerator, SchemaSettings},
|
gen::{SchemaGenerator, SchemaSettings},
|
||||||
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
||||||
|
@ -96,7 +96,7 @@ impl KeymapFile {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_json_schema(action_names: &[&'static str]) -> serde_json::Value {
|
pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
|
||||||
let mut root_schema = SchemaSettings::draft07()
|
let mut root_schema = SchemaSettings::draft07()
|
||||||
.with(|settings| settings.option_add_null_type = false)
|
.with(|settings| settings.option_add_null_type = false)
|
||||||
.into_generator()
|
.into_generator()
|
||||||
|
|
|
@ -108,6 +108,24 @@ pub struct SyntaxTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxTheme {
|
impl SyntaxTheme {
|
||||||
|
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
|
||||||
|
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
|
||||||
|
SyntaxTheme {
|
||||||
|
highlights: colors
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, color)| {
|
||||||
|
(
|
||||||
|
key.to_owned(),
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(color),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> HighlightStyle {
|
pub fn get(&self, name: &str) -> HighlightStyle {
|
||||||
self.highlights
|
self.highlights
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -47,7 +47,7 @@ install_cli = { path = "../install_cli" }
|
||||||
journal2 = { path = "../journal2" }
|
journal2 = { path = "../journal2" }
|
||||||
language2 = { path = "../language2" }
|
language2 = { path = "../language2" }
|
||||||
# language_selector = { path = "../language_selector" }
|
# language_selector = { path = "../language_selector" }
|
||||||
lsp = { path = "../lsp" }
|
lsp2 = { path = "../lsp2" }
|
||||||
language_tools = { path = "../language_tools" }
|
language_tools = { path = "../language_tools" }
|
||||||
node_runtime = { path = "../node_runtime" }
|
node_runtime = { path = "../node_runtime" }
|
||||||
# assistant = { path = "../assistant" }
|
# assistant = { path = "../assistant" }
|
||||||
|
@ -60,7 +60,7 @@ project2 = { path = "../project2" }
|
||||||
# recent_projects = { path = "../recent_projects" }
|
# recent_projects = { path = "../recent_projects" }
|
||||||
rpc2 = { path = "../rpc2" }
|
rpc2 = { path = "../rpc2" }
|
||||||
settings2 = { path = "../settings2" }
|
settings2 = { path = "../settings2" }
|
||||||
feature_flags = { path = "../feature_flags" }
|
feature_flags2 = { path = "../feature_flags2" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
@ -135,6 +135,7 @@ tree-sitter-yaml.workspace = true
|
||||||
tree-sitter-lua.workspace = true
|
tree-sitter-lua.workspace = true
|
||||||
tree-sitter-nix.workspace = true
|
tree-sitter-nix.workspace = true
|
||||||
tree-sitter-nu.workspace = true
|
tree-sitter-nu.workspace = true
|
||||||
|
tree-sitter-vue.workspace = true
|
||||||
|
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
|
273
crates/zed2/src/languages.rs
Normal file
273
crates/zed2/src/languages.rs
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use gpui2::AppContext;
|
||||||
|
pub use language2::*;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use settings2::Settings;
|
||||||
|
use std::{borrow::Cow, str, sync::Arc};
|
||||||
|
use util::asset_str;
|
||||||
|
|
||||||
|
use self::elixir::ElixirSettings;
|
||||||
|
|
||||||
|
mod c;
|
||||||
|
mod css;
|
||||||
|
mod elixir;
|
||||||
|
mod go;
|
||||||
|
mod html;
|
||||||
|
mod json;
|
||||||
|
#[cfg(feature = "plugin_runtime")]
|
||||||
|
mod language_plugin;
|
||||||
|
mod lua;
|
||||||
|
mod php;
|
||||||
|
mod python;
|
||||||
|
mod ruby;
|
||||||
|
mod rust;
|
||||||
|
mod svelte;
|
||||||
|
mod tailwind;
|
||||||
|
mod typescript;
|
||||||
|
mod vue;
|
||||||
|
mod yaml;
|
||||||
|
|
||||||
|
// 1. Add tree-sitter-{language} parser to zed crate
|
||||||
|
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
|
||||||
|
// 3. Add config.toml to the newly created language directory using existing languages as a template
|
||||||
|
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
|
||||||
|
// Note: github highlights take the last match while zed takes the first
|
||||||
|
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
|
||||||
|
// and autoclosing brackets respectively
|
||||||
|
// 6. If the language has injections add an injections.scm query file
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "src/languages"]
|
||||||
|
#[exclude = "*.rs"]
|
||||||
|
struct LanguageDir;
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
node_runtime: Arc<dyn NodeRuntime>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) {
|
||||||
|
ElixirSettings::register(cx);
|
||||||
|
|
||||||
|
let language = |name, grammar, adapters| {
|
||||||
|
languages.register(name, load_config(name), grammar, adapters, load_queries)
|
||||||
|
};
|
||||||
|
|
||||||
|
language("bash", tree_sitter_bash::language(), vec![]);
|
||||||
|
language(
|
||||||
|
"c",
|
||||||
|
tree_sitter_c::language(),
|
||||||
|
vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"cpp",
|
||||||
|
tree_sitter_cpp::language(),
|
||||||
|
vec![Arc::new(c::CLspAdapter)],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"css",
|
||||||
|
tree_sitter_css::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
match &ElixirSettings::get(None, cx).lsp {
|
||||||
|
elixir::ElixirLspSetting::ElixirLs => language(
|
||||||
|
"elixir",
|
||||||
|
tree_sitter_elixir::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(elixir::ElixirLspAdapter),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
elixir::ElixirLspSetting::NextLs => language(
|
||||||
|
"elixir",
|
||||||
|
tree_sitter_elixir::language(),
|
||||||
|
vec![Arc::new(elixir::NextLspAdapter)],
|
||||||
|
),
|
||||||
|
elixir::ElixirLspSetting::Local { path, arguments } => language(
|
||||||
|
"elixir",
|
||||||
|
tree_sitter_elixir::language(),
|
||||||
|
vec![Arc::new(elixir::LocalLspAdapter {
|
||||||
|
path: path.clone(),
|
||||||
|
arguments: arguments.clone(),
|
||||||
|
})],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
language(
|
||||||
|
"go",
|
||||||
|
tree_sitter_go::language(),
|
||||||
|
vec![Arc::new(go::GoLspAdapter)],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"heex",
|
||||||
|
tree_sitter_heex::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(elixir::ElixirLspAdapter),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"json",
|
||||||
|
tree_sitter_json::language(),
|
||||||
|
vec![Arc::new(json::JsonLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
languages.clone(),
|
||||||
|
))],
|
||||||
|
);
|
||||||
|
language("markdown", tree_sitter_markdown::language(), vec![]);
|
||||||
|
language(
|
||||||
|
"python",
|
||||||
|
tree_sitter_python::language(),
|
||||||
|
vec![Arc::new(python::PythonLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
))],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"rust",
|
||||||
|
tree_sitter_rust::language(),
|
||||||
|
vec![Arc::new(rust::RustLspAdapter)],
|
||||||
|
);
|
||||||
|
language("toml", tree_sitter_toml::language(), vec![]);
|
||||||
|
language(
|
||||||
|
"tsx",
|
||||||
|
tree_sitter_typescript::language_tsx(),
|
||||||
|
vec![
|
||||||
|
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"typescript",
|
||||||
|
tree_sitter_typescript::language_typescript(),
|
||||||
|
vec![
|
||||||
|
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"javascript",
|
||||||
|
tree_sitter_typescript::language_tsx(),
|
||||||
|
vec![
|
||||||
|
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"html",
|
||||||
|
tree_sitter_html::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"ruby",
|
||||||
|
tree_sitter_ruby::language(),
|
||||||
|
vec![Arc::new(ruby::RubyLanguageServer)],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"erb",
|
||||||
|
tree_sitter_embedded_template::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(ruby::RubyLanguageServer),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language("scheme", tree_sitter_scheme::language(), vec![]);
|
||||||
|
language("racket", tree_sitter_racket::language(), vec![]);
|
||||||
|
language(
|
||||||
|
"lua",
|
||||||
|
tree_sitter_lua::language(),
|
||||||
|
vec![Arc::new(lua::LuaLspAdapter)],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"yaml",
|
||||||
|
tree_sitter_yaml::language(),
|
||||||
|
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"svelte",
|
||||||
|
tree_sitter_svelte::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
language(
|
||||||
|
"php",
|
||||||
|
tree_sitter_php::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
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![]);
|
||||||
|
language(
|
||||||
|
"vue",
|
||||||
|
tree_sitter_vue::language(),
|
||||||
|
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub async fn language(
|
||||||
|
name: &str,
|
||||||
|
grammar: tree_sitter::Language,
|
||||||
|
lsp_adapter: Option<Arc<dyn LspAdapter>>,
|
||||||
|
) -> Arc<Language> {
|
||||||
|
Arc::new(
|
||||||
|
Language::new(load_config(name), Some(grammar))
|
||||||
|
.with_lsp_adapters(lsp_adapter.into_iter().collect())
|
||||||
|
.await
|
||||||
|
.with_queries(load_queries(name))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_config(name: &str) -> LanguageConfig {
|
||||||
|
toml::from_slice(
|
||||||
|
&LanguageDir::get(&format!("{}/config.toml", name))
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("failed to load config.toml for language {name:?}"))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_queries(name: &str) -> LanguageQueries {
|
||||||
|
LanguageQueries {
|
||||||
|
highlights: load_query(name, "/highlights"),
|
||||||
|
brackets: load_query(name, "/brackets"),
|
||||||
|
indents: load_query(name, "/indents"),
|
||||||
|
outline: load_query(name, "/outline"),
|
||||||
|
embedding: load_query(name, "/embedding"),
|
||||||
|
injections: load_query(name, "/injections"),
|
||||||
|
overrides: load_query(name, "/overrides"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
|
||||||
|
let mut result = None;
|
||||||
|
for path in LanguageDir::iter() {
|
||||||
|
if let Some(remainder) = path.strip_prefix(name) {
|
||||||
|
if remainder.starts_with(filename_prefix) {
|
||||||
|
let contents = asset_str::<LanguageDir>(path.as_ref());
|
||||||
|
match &mut result {
|
||||||
|
None => result = Some(contents),
|
||||||
|
Some(r) => r.to_mut().push_str(contents.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
3
crates/zed2/src/languages/bash/brackets.scm
Normal file
3
crates/zed2/src/languages/bash/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
9
crates/zed2/src/languages/bash/config.toml
Normal file
9
crates/zed2/src/languages/bash/config.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name = "Shell Script"
|
||||||
|
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"]
|
||||||
|
line_comment = "# "
|
||||||
|
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
|
||||||
|
brackets = [
|
||||||
|
{ start = "[", end = "]", close = true, newline = false },
|
||||||
|
{ start = "(", end = ")", close = true, newline = false },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
]
|
59
crates/zed2/src/languages/bash/highlights.scm
Normal file
59
crates/zed2/src/languages/bash/highlights.scm
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(raw_string)
|
||||||
|
(heredoc_body)
|
||||||
|
(heredoc_start)
|
||||||
|
(ansi_c_string)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(command_name) @function
|
||||||
|
|
||||||
|
(variable_name) @property
|
||||||
|
|
||||||
|
[
|
||||||
|
"case"
|
||||||
|
"do"
|
||||||
|
"done"
|
||||||
|
"elif"
|
||||||
|
"else"
|
||||||
|
"esac"
|
||||||
|
"export"
|
||||||
|
"fi"
|
||||||
|
"for"
|
||||||
|
"function"
|
||||||
|
"if"
|
||||||
|
"in"
|
||||||
|
"select"
|
||||||
|
"then"
|
||||||
|
"unset"
|
||||||
|
"until"
|
||||||
|
"while"
|
||||||
|
"local"
|
||||||
|
"declare"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
(function_definition name: (word) @function)
|
||||||
|
|
||||||
|
(file_descriptor) @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(command_substitution)
|
||||||
|
(process_substitution)
|
||||||
|
(expansion)
|
||||||
|
]@embedded
|
||||||
|
|
||||||
|
[
|
||||||
|
"$"
|
||||||
|
"&&"
|
||||||
|
">"
|
||||||
|
">>"
|
||||||
|
"<"
|
||||||
|
"|"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(
|
||||||
|
(command (_) @constant)
|
||||||
|
(#match? @constant "^-")
|
||||||
|
)
|
321
crates/zed2/src/languages/c.rs
Normal file
321
crates/zed2/src/languages/c.rs
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
pub use language2::*;
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
use smol::fs::{self, File};
|
||||||
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||||
|
use util::{
|
||||||
|
fs::remove_matching,
|
||||||
|
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||||
|
ResultExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CLspAdapter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl super::LspAdapter for CLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("clangd".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"clangd"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let release = latest_github_release("clangd/clangd", false, delegate.http_client()).await?;
|
||||||
|
let asset_name = format!("clangd-mac-{}.zip", release.name);
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||||
|
let version = GitHubLspBinaryVersion {
|
||||||
|
name: release.name,
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
};
|
||||||
|
Ok(Box::new(version) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
|
||||||
|
let version_dir = container_dir.join(format!("clangd_{}", version.name));
|
||||||
|
let binary_path = version_dir.join("bin/clangd");
|
||||||
|
|
||||||
|
if fs::metadata(&binary_path).await.is_err() {
|
||||||
|
let mut response = delegate
|
||||||
|
.http_client()
|
||||||
|
.get(&version.url, Default::default(), true)
|
||||||
|
.await
|
||||||
|
.context("error downloading release")?;
|
||||||
|
let mut file = File::create(&zip_path).await?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"download failed with status {}",
|
||||||
|
response.status().to_string()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||||
|
|
||||||
|
let unzip_status = smol::process::Command::new("unzip")
|
||||||
|
.current_dir(&container_dir)
|
||||||
|
.arg(&zip_path)
|
||||||
|
.output()
|
||||||
|
.await?
|
||||||
|
.status;
|
||||||
|
if !unzip_status.success() {
|
||||||
|
Err(anyhow!("failed to unzip clangd archive"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_matching(&container_dir, |entry| entry != version_dir).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir)
|
||||||
|
.await
|
||||||
|
.map(|mut binary| {
|
||||||
|
binary.arguments = vec!["--help".into()];
|
||||||
|
binary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
let label = completion
|
||||||
|
.label
|
||||||
|
.strip_prefix('•')
|
||||||
|
.unwrap_or(&completion.label)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
match completion.kind {
|
||||||
|
Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => {
|
||||||
|
let detail = completion.detail.as_ref().unwrap();
|
||||||
|
let text = format!("{} {}", detail, label);
|
||||||
|
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
|
||||||
|
let runs = language.highlight_text(&source, 11..11 + text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
filter_range: detail.len() + 1..text.len(),
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE)
|
||||||
|
if completion.detail.is_some() =>
|
||||||
|
{
|
||||||
|
let detail = completion.detail.as_ref().unwrap();
|
||||||
|
let text = format!("{} {}", detail, label);
|
||||||
|
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
filter_range: detail.len() + 1..text.len(),
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD)
|
||||||
|
if completion.detail.is_some() =>
|
||||||
|
{
|
||||||
|
let detail = completion.detail.as_ref().unwrap();
|
||||||
|
let text = format!("{} {}", detail, label);
|
||||||
|
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
filter_range: detail.len() + 1..text.rfind('(').unwrap_or(text.len()),
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(kind) => {
|
||||||
|
let highlight_name = match kind {
|
||||||
|
lsp2::CompletionItemKind::STRUCT
|
||||||
|
| lsp2::CompletionItemKind::INTERFACE
|
||||||
|
| lsp2::CompletionItemKind::CLASS
|
||||||
|
| lsp2::CompletionItemKind::ENUM => Some("type"),
|
||||||
|
lsp2::CompletionItemKind::ENUM_MEMBER => Some("variant"),
|
||||||
|
lsp2::CompletionItemKind::KEYWORD => Some("keyword"),
|
||||||
|
lsp2::CompletionItemKind::VALUE | lsp2::CompletionItemKind::CONSTANT => {
|
||||||
|
Some("constant")
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(highlight_id) = language
|
||||||
|
.grammar()
|
||||||
|
.and_then(|g| g.highlight_id_for_name(highlight_name?))
|
||||||
|
{
|
||||||
|
let mut label = CodeLabel::plain(label.to_string(), None);
|
||||||
|
label.runs.push((
|
||||||
|
0..label.text.rfind('(').unwrap_or(label.text.len()),
|
||||||
|
highlight_id,
|
||||||
|
));
|
||||||
|
return Some(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Some(CodeLabel::plain(label.to_string(), None))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
kind: lsp2::SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
let (text, filter_range, display_range) = match kind {
|
||||||
|
lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
|
||||||
|
let text = format!("void {} () {{}}", name);
|
||||||
|
let filter_range = 0..name.len();
|
||||||
|
let display_range = 5..5 + name.len();
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::STRUCT => {
|
||||||
|
let text = format!("struct {} {{}}", name);
|
||||||
|
let filter_range = 7..7 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::ENUM => {
|
||||||
|
let text = format!("enum {} {{}}", name);
|
||||||
|
let filter_range = 5..5 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::INTERFACE | lsp2::SymbolKind::CLASS => {
|
||||||
|
let text = format!("class {} {{}}", name);
|
||||||
|
let filter_range = 6..6 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::CONSTANT => {
|
||||||
|
let text = format!("const int {} = 0;", name);
|
||||||
|
let filter_range = 10..10 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::MODULE => {
|
||||||
|
let text = format!("namespace {} {{}}", name);
|
||||||
|
let filter_range = 10..10 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::TYPE_PARAMETER => {
|
||||||
|
let text = format!("typename {} {{}};", name);
|
||||||
|
let filter_range = 9..9 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||||
|
text: text[display_range].to_string(),
|
||||||
|
filter_range,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_clangd_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_clangd_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let clangd_bin = clangd_dir.join("bin/clangd");
|
||||||
|
if clangd_bin.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: clangd_bin,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing clangd binary in directory {:?}",
|
||||||
|
clangd_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use gpui2::{Context, TestAppContext};
|
||||||
|
use language2::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
|
||||||
|
use settings2::SettingsStore;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
#[gpui2::test]
|
||||||
|
async fn test_c_autoindent(cx: &mut TestAppContext) {
|
||||||
|
// cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||||
|
cx.update(|cx| {
|
||||||
|
let test_settings = SettingsStore::test(cx);
|
||||||
|
cx.set_global(test_settings);
|
||||||
|
language2::init(cx);
|
||||||
|
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||||
|
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
|
||||||
|
s.defaults.tab_size = NonZeroU32::new(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
|
||||||
|
|
||||||
|
cx.build_model(|cx| {
|
||||||
|
let mut buffer =
|
||||||
|
Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
|
||||||
|
|
||||||
|
// empty function
|
||||||
|
buffer.edit([(0..0, "int main() {}")], None, cx);
|
||||||
|
|
||||||
|
// indent inside braces
|
||||||
|
let ix = buffer.len() - 1;
|
||||||
|
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
|
||||||
|
assert_eq!(buffer.text(), "int main() {\n \n}");
|
||||||
|
|
||||||
|
// indent body of single-statement if statement
|
||||||
|
let ix = buffer.len() - 2;
|
||||||
|
buffer.edit([(ix..ix, "if (a)\nb;")], Some(AutoindentMode::EachLine), cx);
|
||||||
|
assert_eq!(buffer.text(), "int main() {\n if (a)\n b;\n}");
|
||||||
|
|
||||||
|
// indent inside field expression
|
||||||
|
let ix = buffer.len() - 3;
|
||||||
|
buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
|
||||||
|
assert_eq!(buffer.text(), "int main() {\n if (a)\n b\n .c;\n}");
|
||||||
|
|
||||||
|
buffer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
3
crates/zed2/src/languages/c/brackets.scm
Normal file
3
crates/zed2/src/languages/c/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("\"" @open "\"" @close)
|
12
crates/zed2/src/languages/c/config.toml
Normal file
12
crates/zed2/src/languages/c/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name = "C"
|
||||||
|
path_suffixes = ["c"]
|
||||||
|
line_comment = "// "
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
]
|
43
crates/zed2/src/languages/c/embedding.scm
Normal file
43
crates/zed2/src/languages/c/embedding.scm
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(declaration
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name)
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name)))
|
||||||
|
]
|
||||||
|
) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(function_definition
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
)
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name)))
|
||||||
|
]
|
||||||
|
) @item
|
||||||
|
)
|
109
crates/zed2/src/languages/c/highlights.scm
Normal file
109
crates/zed2/src/languages/c/highlights.scm
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
[
|
||||||
|
"break"
|
||||||
|
"case"
|
||||||
|
"const"
|
||||||
|
"continue"
|
||||||
|
"default"
|
||||||
|
"do"
|
||||||
|
"else"
|
||||||
|
"enum"
|
||||||
|
"extern"
|
||||||
|
"for"
|
||||||
|
"if"
|
||||||
|
"inline"
|
||||||
|
"return"
|
||||||
|
"sizeof"
|
||||||
|
"static"
|
||||||
|
"struct"
|
||||||
|
"switch"
|
||||||
|
"typedef"
|
||||||
|
"union"
|
||||||
|
"volatile"
|
||||||
|
"while"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"#define"
|
||||||
|
"#elif"
|
||||||
|
"#else"
|
||||||
|
"#endif"
|
||||||
|
"#if"
|
||||||
|
"#ifdef"
|
||||||
|
"#ifndef"
|
||||||
|
"#include"
|
||||||
|
(preproc_directive)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"--"
|
||||||
|
"-"
|
||||||
|
"-="
|
||||||
|
"->"
|
||||||
|
"="
|
||||||
|
"!="
|
||||||
|
"*"
|
||||||
|
"&"
|
||||||
|
"&&"
|
||||||
|
"+"
|
||||||
|
"++"
|
||||||
|
"+="
|
||||||
|
"<"
|
||||||
|
"=="
|
||||||
|
">"
|
||||||
|
"||"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"."
|
||||||
|
";"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
(string_literal)
|
||||||
|
(system_lib_string)
|
||||||
|
(char_literal)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
(number_literal) @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
(null)
|
||||||
|
] @constant
|
||||||
|
|
||||||
|
(identifier) @variable
|
||||||
|
|
||||||
|
((identifier) @constant
|
||||||
|
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @function)
|
||||||
|
(call_expression
|
||||||
|
function: (field_expression
|
||||||
|
field: (field_identifier) @function))
|
||||||
|
(function_declarator
|
||||||
|
declarator: (identifier) @function)
|
||||||
|
(preproc_function_def
|
||||||
|
name: (identifier) @function.special)
|
||||||
|
|
||||||
|
(field_identifier) @property
|
||||||
|
(statement_identifier) @label
|
||||||
|
|
||||||
|
[
|
||||||
|
(type_identifier)
|
||||||
|
(primitive_type)
|
||||||
|
(sized_type_specifier)
|
||||||
|
] @type
|
||||||
|
|
9
crates/zed2/src/languages/c/indents.scm
Normal file
9
crates/zed2/src/languages/c/indents.scm
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[
|
||||||
|
(field_expression)
|
||||||
|
(assignment_expression)
|
||||||
|
(if_statement)
|
||||||
|
(for_statement)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent
|
7
crates/zed2/src/languages/c/injections.scm
Normal file
7
crates/zed2/src/languages/c/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(preproc_def
|
||||||
|
value: (preproc_arg) @content
|
||||||
|
(#set! "language" "c"))
|
||||||
|
|
||||||
|
(preproc_function_def
|
||||||
|
value: (preproc_arg) @content
|
||||||
|
(#set! "language" "c"))
|
70
crates/zed2/src/languages/c/outline.scm
Normal file
70
crates/zed2/src/languages/c/outline.scm
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
(preproc_def
|
||||||
|
"#define" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(preproc_function_def
|
||||||
|
"#define" @context
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (preproc_params
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(type_definition
|
||||||
|
"typedef" @context
|
||||||
|
declarator: (_) @name) @item
|
||||||
|
|
||||||
|
(declaration
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_)? @context
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))))
|
||||||
|
]
|
||||||
|
) @item
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_)? @context
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))))
|
||||||
|
]
|
||||||
|
) @item
|
2
crates/zed2/src/languages/c/overrides.scm
Normal file
2
crates/zed2/src/languages/c/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(comment) @comment
|
||||||
|
(string_literal) @string
|
3
crates/zed2/src/languages/cpp/brackets.scm
Normal file
3
crates/zed2/src/languages/cpp/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("\"" @open "\"" @close)
|
12
crates/zed2/src/languages/cpp/config.toml
Normal file
12
crates/zed2/src/languages/cpp/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name = "C++"
|
||||||
|
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
|
||||||
|
line_comment = "// "
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
]
|
61
crates/zed2/src/languages/cpp/embedding.scm
Normal file
61
crates/zed2/src/languages/cpp/embedding.scm
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(function_definition
|
||||||
|
(type_qualifier)? @name
|
||||||
|
type: (_)? @name
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name)
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @name
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name)))
|
||||||
|
(reference_declarator
|
||||||
|
["&" "&&"] @name
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name))
|
||||||
|
]
|
||||||
|
(type_qualifier)? @name) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(template_declaration
|
||||||
|
(class_specifier
|
||||||
|
"class" @name
|
||||||
|
name: (_) @name)
|
||||||
|
) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(class_specifier
|
||||||
|
"class" @name
|
||||||
|
name: (_) @name) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(enum_specifier
|
||||||
|
"enum" @name
|
||||||
|
name: (_) @name) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(declaration
|
||||||
|
type: (struct_specifier
|
||||||
|
"struct" @name)
|
||||||
|
declarator: (_) @name) @item
|
||||||
|
)
|
158
crates/zed2/src/languages/cpp/highlights.scm
Normal file
158
crates/zed2/src/languages/cpp/highlights.scm
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
(identifier) @variable
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (qualified_identifier
|
||||||
|
name: (identifier) @function))
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @function)
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (field_expression
|
||||||
|
field: (field_identifier) @function))
|
||||||
|
|
||||||
|
(preproc_function_def
|
||||||
|
name: (identifier) @function.special)
|
||||||
|
|
||||||
|
(template_function
|
||||||
|
name: (identifier) @function)
|
||||||
|
|
||||||
|
(template_method
|
||||||
|
name: (field_identifier) @function)
|
||||||
|
|
||||||
|
(function_declarator
|
||||||
|
declarator: (identifier) @function)
|
||||||
|
|
||||||
|
(function_declarator
|
||||||
|
declarator: (qualified_identifier
|
||||||
|
name: (identifier) @function))
|
||||||
|
|
||||||
|
(function_declarator
|
||||||
|
declarator: (field_identifier) @function)
|
||||||
|
|
||||||
|
((namespace_identifier) @type
|
||||||
|
(#match? @type "^[A-Z]"))
|
||||||
|
|
||||||
|
(auto) @type
|
||||||
|
(type_identifier) @type
|
||||||
|
|
||||||
|
((identifier) @constant
|
||||||
|
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
|
||||||
|
|
||||||
|
(field_identifier) @property
|
||||||
|
(statement_identifier) @label
|
||||||
|
(this) @variable.special
|
||||||
|
|
||||||
|
[
|
||||||
|
"break"
|
||||||
|
"case"
|
||||||
|
"catch"
|
||||||
|
"class"
|
||||||
|
"co_await"
|
||||||
|
"co_return"
|
||||||
|
"co_yield"
|
||||||
|
"const"
|
||||||
|
"constexpr"
|
||||||
|
"continue"
|
||||||
|
"default"
|
||||||
|
"delete"
|
||||||
|
"do"
|
||||||
|
"else"
|
||||||
|
"enum"
|
||||||
|
"explicit"
|
||||||
|
"extern"
|
||||||
|
"final"
|
||||||
|
"for"
|
||||||
|
"friend"
|
||||||
|
"if"
|
||||||
|
"if"
|
||||||
|
"inline"
|
||||||
|
"mutable"
|
||||||
|
"namespace"
|
||||||
|
"new"
|
||||||
|
"noexcept"
|
||||||
|
"override"
|
||||||
|
"private"
|
||||||
|
"protected"
|
||||||
|
"public"
|
||||||
|
"return"
|
||||||
|
"sizeof"
|
||||||
|
"static"
|
||||||
|
"struct"
|
||||||
|
"switch"
|
||||||
|
"template"
|
||||||
|
"throw"
|
||||||
|
"try"
|
||||||
|
"typedef"
|
||||||
|
"typename"
|
||||||
|
"union"
|
||||||
|
"using"
|
||||||
|
"virtual"
|
||||||
|
"volatile"
|
||||||
|
"while"
|
||||||
|
(primitive_type)
|
||||||
|
(type_qualifier)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"#define"
|
||||||
|
"#elif"
|
||||||
|
"#else"
|
||||||
|
"#endif"
|
||||||
|
"#if"
|
||||||
|
"#ifdef"
|
||||||
|
"#ifndef"
|
||||||
|
"#include"
|
||||||
|
(preproc_directive)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
(null)
|
||||||
|
(nullptr)
|
||||||
|
] @constant
|
||||||
|
|
||||||
|
(number_literal) @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(string_literal)
|
||||||
|
(system_lib_string)
|
||||||
|
(char_literal)
|
||||||
|
(raw_string_literal)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
[
|
||||||
|
"."
|
||||||
|
";"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
"--"
|
||||||
|
"-"
|
||||||
|
"-="
|
||||||
|
"->"
|
||||||
|
"="
|
||||||
|
"!="
|
||||||
|
"*"
|
||||||
|
"&"
|
||||||
|
"&&"
|
||||||
|
"+"
|
||||||
|
"++"
|
||||||
|
"+="
|
||||||
|
"<"
|
||||||
|
"=="
|
||||||
|
">"
|
||||||
|
"||"
|
||||||
|
] @operator
|
7
crates/zed2/src/languages/cpp/indents.scm
Normal file
7
crates/zed2/src/languages/cpp/indents.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[
|
||||||
|
(field_expression)
|
||||||
|
(assignment_expression)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent
|
7
crates/zed2/src/languages/cpp/injections.scm
Normal file
7
crates/zed2/src/languages/cpp/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(preproc_def
|
||||||
|
value: (preproc_arg) @content
|
||||||
|
(#set! "language" "c++"))
|
||||||
|
|
||||||
|
(preproc_function_def
|
||||||
|
value: (preproc_arg) @content
|
||||||
|
(#set! "language" "c++"))
|
149
crates/zed2/src/languages/cpp/outline.scm
Normal file
149
crates/zed2/src/languages/cpp/outline.scm
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
(preproc_def
|
||||||
|
"#define" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(preproc_function_def
|
||||||
|
"#define" @context
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (preproc_params
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(type_definition
|
||||||
|
"typedef" @context
|
||||||
|
declarator: (_) @name) @item
|
||||||
|
|
||||||
|
(struct_specifier
|
||||||
|
"struct" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(class_specifier
|
||||||
|
"class" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(enum_specifier
|
||||||
|
"enum" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(enumerator
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(declaration
|
||||||
|
(storage_class_specifier) @context
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_) @context
|
||||||
|
declarator: (init_declarator
|
||||||
|
declarator: (_) @name)) @item
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_)? @context
|
||||||
|
declarator: [
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))))
|
||||||
|
(reference_declarator
|
||||||
|
["&" "&&"] @context
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
]
|
||||||
|
(type_qualifier)? @context) @item
|
||||||
|
|
||||||
|
(declaration
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_)? @context
|
||||||
|
declarator: [
|
||||||
|
(field_identifier) @name
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (field_identifier) @name)
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))))
|
||||||
|
(reference_declarator
|
||||||
|
["&" "&&"] @context
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
]
|
||||||
|
(type_qualifier)? @context) @item
|
||||||
|
|
||||||
|
(field_declaration
|
||||||
|
(type_qualifier)? @context
|
||||||
|
type: (_) @context
|
||||||
|
declarator: [
|
||||||
|
(field_identifier) @name
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (field_identifier) @name)
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
(pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (pointer_declarator
|
||||||
|
"*" @context
|
||||||
|
declarator: (function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context))))
|
||||||
|
(reference_declarator
|
||||||
|
["&" "&&"] @context
|
||||||
|
(function_declarator
|
||||||
|
declarator: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)))
|
||||||
|
]
|
||||||
|
(type_qualifier)? @context) @item
|
2
crates/zed2/src/languages/cpp/overrides.scm
Normal file
2
crates/zed2/src/languages/cpp/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(comment) @comment
|
||||||
|
(string_literal) @string
|
130
crates/zed2/src/languages/css.rs
Normal file
130
crates/zed2/src/languages/css.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde_json::json;
|
||||||
|
use smol::fs;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::OsString,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
const SERVER_PATH: &'static str =
|
||||||
|
"node_modules/vscode-langservers-extracted/bin/vscode-css-language-server";
|
||||||
|
|
||||||
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CssLspAdapter {
|
||||||
|
node: Arc<dyn NodeRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CssLspAdapter {
|
||||||
|
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||||
|
CssLspAdapter { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for CssLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("vscode-css-language-server".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"css"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("vscode-langservers-extracted")
|
||||||
|
.await?,
|
||||||
|
) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
&container_dir,
|
||||||
|
&[("vscode-langservers-extracted", version.as_str())],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
Some(json!({
|
||||||
|
"provideFormatter": true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
node: &dyn NodeRuntime,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_version_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_version_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
last_version_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
3
crates/zed2/src/languages/css/brackets.scm
Normal file
3
crates/zed2/src/languages/css/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
13
crates/zed2/src/languages/css/config.toml
Normal file
13
crates/zed2/src/languages/css/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name = "CSS"
|
||||||
|
path_suffixes = ["css"]
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
]
|
||||||
|
word_characters = ["-"]
|
||||||
|
block_comment = ["/* ", " */"]
|
||||||
|
prettier_parser_name = "css"
|
78
crates/zed2/src/languages/css/highlights.scm
Normal file
78
crates/zed2/src/languages/css/highlights.scm
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
(tag_name)
|
||||||
|
(nesting_selector)
|
||||||
|
(universal_selector)
|
||||||
|
] @tag
|
||||||
|
|
||||||
|
[
|
||||||
|
"~"
|
||||||
|
">"
|
||||||
|
"+"
|
||||||
|
"-"
|
||||||
|
"*"
|
||||||
|
"/"
|
||||||
|
"="
|
||||||
|
"^="
|
||||||
|
"|="
|
||||||
|
"~="
|
||||||
|
"$="
|
||||||
|
"*="
|
||||||
|
"and"
|
||||||
|
"or"
|
||||||
|
"not"
|
||||||
|
"only"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(attribute_selector (plain_value) @string)
|
||||||
|
|
||||||
|
(attribute_name) @attribute
|
||||||
|
(pseudo_element_selector (tag_name) @attribute)
|
||||||
|
(pseudo_class_selector (class_name) @attribute)
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_name)
|
||||||
|
(id_name)
|
||||||
|
(namespace_name)
|
||||||
|
(property_name)
|
||||||
|
(feature_name)
|
||||||
|
] @property
|
||||||
|
|
||||||
|
(function_name) @function
|
||||||
|
|
||||||
|
(
|
||||||
|
[
|
||||||
|
(property_name)
|
||||||
|
(plain_value)
|
||||||
|
] @variable.special
|
||||||
|
(#match? @variable.special "^--")
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
"@media"
|
||||||
|
"@import"
|
||||||
|
"@charset"
|
||||||
|
"@namespace"
|
||||||
|
"@supports"
|
||||||
|
"@keyframes"
|
||||||
|
(at_keyword)
|
||||||
|
(to)
|
||||||
|
(from)
|
||||||
|
(important)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(string_value) @string
|
||||||
|
(color_value) @string.special
|
||||||
|
|
||||||
|
[
|
||||||
|
(integer_value)
|
||||||
|
(float_value)
|
||||||
|
] @number
|
||||||
|
|
||||||
|
(unit) @type
|
||||||
|
|
||||||
|
[
|
||||||
|
","
|
||||||
|
":"
|
||||||
|
] @punctuation.delimiter
|
1
crates/zed2/src/languages/css/indents.scm
Normal file
1
crates/zed2/src/languages/css/indents.scm
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(_ "{" "}" @end) @indent
|
2
crates/zed2/src/languages/css/overrides.scm
Normal file
2
crates/zed2/src/languages/css/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(comment) @comment
|
||||||
|
(string_value) @string
|
546
crates/zed2/src/languages/elixir.rs
Normal file
546
crates/zed2/src/languages/elixir.rs
Normal file
|
@ -0,0 +1,546 @@
|
||||||
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use gpui2::{AsyncAppContext, Task};
|
||||||
|
pub use language2::*;
|
||||||
|
use lsp2::{CompletionItemKind, LanguageServerBinary, SymbolKind};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use settings2::Settings;
|
||||||
|
use smol::fs::{self, File};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
env::consts,
|
||||||
|
ops::Deref,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering::SeqCst},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use util::{
|
||||||
|
async_maybe,
|
||||||
|
fs::remove_matching,
|
||||||
|
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||||
|
ResultExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct ElixirSettings {
|
||||||
|
pub lsp: ElixirLspSetting,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ElixirLspSetting {
|
||||||
|
ElixirLs,
|
||||||
|
NextLs,
|
||||||
|
Local {
|
||||||
|
path: String,
|
||||||
|
arguments: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
|
||||||
|
pub struct ElixirSettingsContent {
|
||||||
|
lsp: Option<ElixirLspSetting>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings for ElixirSettings {
|
||||||
|
const KEY: Option<&'static str> = Some("elixir");
|
||||||
|
|
||||||
|
type FileContent = ElixirSettingsContent;
|
||||||
|
|
||||||
|
fn load(
|
||||||
|
default_value: &Self::FileContent,
|
||||||
|
user_values: &[&Self::FileContent],
|
||||||
|
_: &mut gpui2::AppContext,
|
||||||
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Self::load_via_json_merge(default_value, user_values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ElixirLspAdapter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for ElixirLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("elixir-ls".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"elixir-ls"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn will_start_server(
|
||||||
|
&self,
|
||||||
|
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
const NOTIFICATION_MESSAGE: &str = "Could not run the elixir language server, `elixir-ls`, because `elixir` was not found.";
|
||||||
|
|
||||||
|
let delegate = delegate.clone();
|
||||||
|
Some(cx.spawn(|cx| async move {
|
||||||
|
let elixir_output = smol::process::Command::new("elixir")
|
||||||
|
.args(["--version"])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
if elixir_output.is_err() {
|
||||||
|
if DID_SHOW_NOTIFICATION
|
||||||
|
.compare_exchange(false, true, SeqCst, SeqCst)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
cx.update(|cx| {
|
||||||
|
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
return Err(anyhow!("cannot run elixir-ls"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let http = delegate.http_client();
|
||||||
|
let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?;
|
||||||
|
let version_name = release
|
||||||
|
.name
|
||||||
|
.strip_prefix("Release ")
|
||||||
|
.context("Elixir-ls release name does not start with prefix")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let asset_name = format!("elixir-ls-{}.zip", &version_name);
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||||
|
|
||||||
|
let version = GitHubLspBinaryVersion {
|
||||||
|
name: version_name,
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
};
|
||||||
|
Ok(Box::new(version) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
|
||||||
|
let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
|
||||||
|
let binary_path = version_dir.join("language_server.sh");
|
||||||
|
|
||||||
|
if fs::metadata(&binary_path).await.is_err() {
|
||||||
|
let mut response = delegate
|
||||||
|
.http_client()
|
||||||
|
.get(&version.url, Default::default(), true)
|
||||||
|
.await
|
||||||
|
.context("error downloading release")?;
|
||||||
|
let mut file = File::create(&zip_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"download failed with status {}",
|
||||||
|
response.status().to_string()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||||
|
|
||||||
|
fs::create_dir_all(&version_dir)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to create directory {}", version_dir.display()))?;
|
||||||
|
let unzip_status = smol::process::Command::new("unzip")
|
||||||
|
.arg(&zip_path)
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&version_dir)
|
||||||
|
.output()
|
||||||
|
.await?
|
||||||
|
.status;
|
||||||
|
if !unzip_status.success() {
|
||||||
|
Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_matching(&container_dir, |entry| entry != version_dir).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary_elixir_ls(container_dir).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary_elixir_ls(container_dir).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
match completion.kind.zip(completion.detail.as_ref()) {
|
||||||
|
Some((_, detail)) if detail.starts_with("(function)") => {
|
||||||
|
let text = detail.strip_prefix("(function) ")?;
|
||||||
|
let filter_range = 0..text.find('(').unwrap_or(text.len());
|
||||||
|
let source = Rope::from(format!("def {text}").as_str());
|
||||||
|
let runs = language.highlight_text(&source, 4..4 + text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text: text.to_string(),
|
||||||
|
runs,
|
||||||
|
filter_range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((_, detail)) if detail.starts_with("(macro)") => {
|
||||||
|
let text = detail.strip_prefix("(macro) ")?;
|
||||||
|
let filter_range = 0..text.find('(').unwrap_or(text.len());
|
||||||
|
let source = Rope::from(format!("defmacro {text}").as_str());
|
||||||
|
let runs = language.highlight_text(&source, 9..9 + text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text: text.to_string(),
|
||||||
|
runs,
|
||||||
|
filter_range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
CompletionItemKind::CLASS
|
||||||
|
| CompletionItemKind::MODULE
|
||||||
|
| CompletionItemKind::INTERFACE
|
||||||
|
| CompletionItemKind::STRUCT,
|
||||||
|
_,
|
||||||
|
)) => {
|
||||||
|
let filter_range = 0..completion
|
||||||
|
.label
|
||||||
|
.find(" (")
|
||||||
|
.unwrap_or(completion.label.len());
|
||||||
|
let text = &completion.label[filter_range.clone()];
|
||||||
|
let source = Rope::from(format!("defmodule {text}").as_str());
|
||||||
|
let runs = language.highlight_text(&source, 10..10 + text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text: completion.label.clone(),
|
||||||
|
runs,
|
||||||
|
filter_range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
kind: SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
let (text, filter_range, display_range) = match kind {
|
||||||
|
SymbolKind::METHOD | SymbolKind::FUNCTION => {
|
||||||
|
let text = format!("def {}", name);
|
||||||
|
let filter_range = 4..4 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => {
|
||||||
|
let text = format!("defmodule {}", name);
|
||||||
|
let filter_range = 10..10 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||||
|
text: text[display_range].to_string(),
|
||||||
|
filter_range,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary_elixir_ls(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
last = Some(entry?.path());
|
||||||
|
}
|
||||||
|
last.map(|path| LanguageServerBinary {
|
||||||
|
path,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow!("no cached binary"))
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NextLspAdapter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for NextLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("next-ls".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"next-ls"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let release =
|
||||||
|
latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
|
||||||
|
let version = release.name.clone();
|
||||||
|
let platform = match consts::ARCH {
|
||||||
|
"x86_64" => "darwin_amd64",
|
||||||
|
"aarch64" => "darwin_arm64",
|
||||||
|
other => bail!("Running on unsupported platform: {other}"),
|
||||||
|
};
|
||||||
|
let asset_name = format!("next_ls_{}", platform);
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||||
|
let version = GitHubLspBinaryVersion {
|
||||||
|
name: version,
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
};
|
||||||
|
Ok(Box::new(version) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
|
||||||
|
let binary_path = container_dir.join("next-ls");
|
||||||
|
|
||||||
|
if fs::metadata(&binary_path).await.is_err() {
|
||||||
|
let mut response = delegate
|
||||||
|
.http_client()
|
||||||
|
.get(&version.url, Default::default(), true)
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||||
|
|
||||||
|
let mut file = smol::fs::File::create(&binary_path).await?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"download failed with status {}",
|
||||||
|
response.status().to_string()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||||
|
|
||||||
|
fs::set_permissions(
|
||||||
|
&binary_path,
|
||||||
|
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec!["--stdio".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary_next(container_dir)
|
||||||
|
.await
|
||||||
|
.map(|mut binary| {
|
||||||
|
binary.arguments = vec!["--stdio".into()];
|
||||||
|
binary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary_next(container_dir)
|
||||||
|
.await
|
||||||
|
.map(|mut binary| {
|
||||||
|
binary.arguments = vec!["--help".into()];
|
||||||
|
binary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
label_for_completion_elixir(completion, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
symbol_kind: SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
label_for_symbol_elixir(name, symbol_kind, language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
async_maybe!({
|
||||||
|
let mut last_binary_path = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_file()
|
||||||
|
&& entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.map_or(false, |name| name == "next-ls")
|
||||||
|
{
|
||||||
|
last_binary_path = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = last_binary_path {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path,
|
||||||
|
arguments: Vec::new(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no cached binary"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalLspAdapter {
|
||||||
|
pub path: String,
|
||||||
|
pub arguments: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for LocalLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("local-ls".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"local-ls"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
Ok(Box::new(()) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
_: Box<dyn 'static + Send + Any>,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let path = shellexpand::full(&self.path)?;
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: PathBuf::from(path.deref()),
|
||||||
|
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
_: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
let path = shellexpand::full(&self.path).ok()?;
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: PathBuf::from(path.deref()),
|
||||||
|
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
let path = shellexpand::full(&self.path).ok()?;
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: PathBuf::from(path.deref()),
|
||||||
|
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
label_for_completion_elixir(completion, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
symbol: SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
label_for_symbol_elixir(name, symbol, language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_for_completion_elixir(
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
return Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
|
||||||
|
text: completion.label.clone(),
|
||||||
|
filter_range: 0..completion.label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_for_symbol_elixir(
|
||||||
|
name: &str,
|
||||||
|
_: SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&name.into(), 0..name.len()),
|
||||||
|
text: name.to_string(),
|
||||||
|
filter_range: 0..name.len(),
|
||||||
|
})
|
||||||
|
}
|
5
crates/zed2/src/languages/elixir/brackets.scm
Normal file
5
crates/zed2/src/languages/elixir/brackets.scm
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("\"" @open "\"" @close)
|
||||||
|
("do" @open "end" @close)
|
16
crates/zed2/src/languages/elixir/config.toml
Normal file
16
crates/zed2/src/languages/elixir/config.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
name = "Elixir"
|
||||||
|
path_suffixes = ["ex", "exs"]
|
||||||
|
line_comment = "# "
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||||
|
|
||||||
|
[overrides.string]
|
||||||
|
word_characters = ["-"]
|
||||||
|
opt_into_language_servers = ["tailwindcss-language-server"]
|
27
crates/zed2/src/languages/elixir/embedding.scm
Normal file
27
crates/zed2/src/languages/elixir/embedding.scm
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(
|
||||||
|
(unary_operator
|
||||||
|
operator: "@"
|
||||||
|
operand: (call
|
||||||
|
target: (identifier) @unary
|
||||||
|
(#match? @unary "^(doc)$"))
|
||||||
|
) @context
|
||||||
|
.
|
||||||
|
(call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(identifier) @name
|
||||||
|
(call
|
||||||
|
target: (identifier) @name)
|
||||||
|
(binary_operator
|
||||||
|
left: (call
|
||||||
|
target: (identifier) @name)
|
||||||
|
operator: "when")
|
||||||
|
])
|
||||||
|
(#match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments (alias) @name)
|
||||||
|
(#match? @name "^(defmodule|defprotocol)$")) @item
|
153
crates/zed2/src/languages/elixir/highlights.scm
Normal file
153
crates/zed2/src/languages/elixir/highlights.scm
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
|
||||||
|
|
||||||
|
(unary_operator
|
||||||
|
operator: "&"
|
||||||
|
operand: (integer) @operator)
|
||||||
|
|
||||||
|
(operator_identifier) @operator
|
||||||
|
|
||||||
|
(unary_operator
|
||||||
|
operator: _ @operator)
|
||||||
|
|
||||||
|
(binary_operator
|
||||||
|
operator: _ @operator)
|
||||||
|
|
||||||
|
(dot
|
||||||
|
operator: _ @operator)
|
||||||
|
|
||||||
|
(stab_clause
|
||||||
|
operator: _ @operator)
|
||||||
|
|
||||||
|
[
|
||||||
|
(boolean)
|
||||||
|
(nil)
|
||||||
|
] @constant
|
||||||
|
|
||||||
|
[
|
||||||
|
(integer)
|
||||||
|
(float)
|
||||||
|
] @number
|
||||||
|
|
||||||
|
(alias) @type
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (dot
|
||||||
|
left: (atom) @type))
|
||||||
|
|
||||||
|
(char) @constant
|
||||||
|
|
||||||
|
(escape_sequence) @string.escape
|
||||||
|
|
||||||
|
[
|
||||||
|
(atom)
|
||||||
|
(quoted_atom)
|
||||||
|
(keyword)
|
||||||
|
(quoted_keyword)
|
||||||
|
] @string.special.symbol
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(charlist)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string
|
||||||
|
quoted_end: _ @string
|
||||||
|
(#match? @__name__ "^[sS]$")) @string
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string.regex
|
||||||
|
quoted_end: _ @string.regex
|
||||||
|
(#match? @__name__ "^[rR]$")) @string.regex
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string.special
|
||||||
|
quoted_end: _ @string.special) @string.special
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @comment.unused
|
||||||
|
(#match? @comment.unused "^_")
|
||||||
|
)
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: [
|
||||||
|
(identifier) @function
|
||||||
|
(dot
|
||||||
|
right: (identifier) @function)
|
||||||
|
])
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(identifier) @function
|
||||||
|
(binary_operator
|
||||||
|
left: (identifier) @function
|
||||||
|
operator: "when")
|
||||||
|
(binary_operator
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier))
|
||||||
|
])
|
||||||
|
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
||||||
|
|
||||||
|
(binary_operator
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier) @function)
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$"))
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$"))
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @constant.builtin
|
||||||
|
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(unary_operator
|
||||||
|
operator: "@" @comment.doc
|
||||||
|
operand: (call
|
||||||
|
target: (identifier) @__attribute__ @comment.doc
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(charlist)
|
||||||
|
(sigil)
|
||||||
|
(boolean)
|
||||||
|
] @comment.doc))
|
||||||
|
(#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
"%"
|
||||||
|
] @punctuation
|
||||||
|
|
||||||
|
[
|
||||||
|
","
|
||||||
|
";"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"<<"
|
||||||
|
">>"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded
|
||||||
|
|
||||||
|
((sigil
|
||||||
|
(sigil_name) @_sigil_name
|
||||||
|
(quoted_content) @embedded)
|
||||||
|
(#eq? @_sigil_name "H"))
|
6
crates/zed2/src/languages/elixir/indents.scm
Normal file
6
crates/zed2/src/languages/elixir/indents.scm
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(call) @indent
|
||||||
|
|
||||||
|
(_ "[" "]" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent
|
||||||
|
(_ "do" "end" @end) @indent
|
7
crates/zed2/src/languages/elixir/injections.scm
Normal file
7
crates/zed2/src/languages/elixir/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
; Phoenix HTML template
|
||||||
|
|
||||||
|
((sigil
|
||||||
|
(sigil_name) @_sigil_name
|
||||||
|
(quoted_content) @content)
|
||||||
|
(#eq? @_sigil_name "H")
|
||||||
|
(#set! language "heex"))
|
26
crates/zed2/src/languages/elixir/outline.scm
Normal file
26
crates/zed2/src/languages/elixir/outline.scm
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(call
|
||||||
|
target: (identifier) @context
|
||||||
|
(arguments (alias) @name)
|
||||||
|
(#match? @context "^(defmodule|defprotocol)$")) @item
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (identifier) @context
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(identifier) @name
|
||||||
|
(call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments
|
||||||
|
"(" @context.extra
|
||||||
|
_* @context.extra
|
||||||
|
")" @context.extra))
|
||||||
|
(binary_operator
|
||||||
|
left: (call
|
||||||
|
target: (identifier) @name
|
||||||
|
(arguments
|
||||||
|
"(" @context.extra
|
||||||
|
_* @context.extra
|
||||||
|
")" @context.extra))
|
||||||
|
operator: "when")
|
||||||
|
])
|
||||||
|
(#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
|
2
crates/zed2/src/languages/elixir/overrides.scm
Normal file
2
crates/zed2/src/languages/elixir/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(comment) @comment
|
||||||
|
[(string) (charlist)] @string
|
11
crates/zed2/src/languages/elm/config.toml
Normal file
11
crates/zed2/src/languages/elm/config.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
name = "Elm"
|
||||||
|
path_suffixes = ["elm"]
|
||||||
|
line_comment = "-- "
|
||||||
|
block_comment = ["{- ", " -}"]
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
|
]
|
72
crates/zed2/src/languages/elm/highlights.scm
Normal file
72
crates/zed2/src/languages/elm/highlights.scm
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
[
|
||||||
|
"if"
|
||||||
|
"then"
|
||||||
|
"else"
|
||||||
|
"let"
|
||||||
|
"in"
|
||||||
|
(case)
|
||||||
|
(of)
|
||||||
|
(backslash)
|
||||||
|
(as)
|
||||||
|
(port)
|
||||||
|
(exposing)
|
||||||
|
(alias)
|
||||||
|
(import)
|
||||||
|
(module)
|
||||||
|
(type)
|
||||||
|
(arrow)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
(eq)
|
||||||
|
(operator_identifier)
|
||||||
|
(colon)
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(type_annotation(lower_case_identifier) @function)
|
||||||
|
(port_annotation(lower_case_identifier) @function)
|
||||||
|
(function_declaration_left(lower_case_identifier) @function.definition)
|
||||||
|
|
||||||
|
(function_call_expr
|
||||||
|
target: (value_expr
|
||||||
|
name: (value_qid (lower_case_identifier) @function)))
|
||||||
|
|
||||||
|
(exposed_value(lower_case_identifier) @function)
|
||||||
|
(exposed_type(upper_case_identifier) @type)
|
||||||
|
|
||||||
|
(field_access_expr(value_expr(value_qid)) @identifier)
|
||||||
|
(lower_pattern) @variable
|
||||||
|
(record_base_identifier) @identifier
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
"|"
|
||||||
|
","
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
(number_constant_expr) @constant
|
||||||
|
|
||||||
|
(type_declaration(upper_case_identifier) @type)
|
||||||
|
(type_ref) @type
|
||||||
|
(type_alias_declaration name: (upper_case_identifier) @type)
|
||||||
|
|
||||||
|
(value_expr(upper_case_qid(upper_case_identifier)) @type)
|
||||||
|
|
||||||
|
[
|
||||||
|
(line_comment)
|
||||||
|
(block_comment)
|
||||||
|
] @comment
|
||||||
|
|
||||||
|
(string_escape) @string.escape
|
||||||
|
|
||||||
|
[
|
||||||
|
(open_quote)
|
||||||
|
(close_quote)
|
||||||
|
(regular_string_part)
|
||||||
|
(open_char)
|
||||||
|
(close_char)
|
||||||
|
] @string
|
2
crates/zed2/src/languages/elm/injections.scm
Normal file
2
crates/zed2/src/languages/elm/injections.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
((glsl_content) @content
|
||||||
|
(#set! "language" "glsl"))
|
22
crates/zed2/src/languages/elm/outline.scm
Normal file
22
crates/zed2/src/languages/elm/outline.scm
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
(type_declaration
|
||||||
|
(type) @context
|
||||||
|
(upper_case_identifier) @name) @item
|
||||||
|
|
||||||
|
(type_alias_declaration
|
||||||
|
(type) @context
|
||||||
|
(alias) @context
|
||||||
|
name: (upper_case_identifier) @name) @item
|
||||||
|
|
||||||
|
(type_alias_declaration
|
||||||
|
typeExpression:
|
||||||
|
(type_expression
|
||||||
|
part: (record_type
|
||||||
|
(field_type
|
||||||
|
name: (lower_case_identifier) @name) @item)))
|
||||||
|
|
||||||
|
(union_variant
|
||||||
|
name: (upper_case_identifier) @name) @item
|
||||||
|
|
||||||
|
(value_declaration
|
||||||
|
functionDeclarationLeft:
|
||||||
|
(function_declaration_left(lower_case_identifier) @name)) @item
|
8
crates/zed2/src/languages/erb/config.toml
Normal file
8
crates/zed2/src/languages/erb/config.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
name = "ERB"
|
||||||
|
path_suffixes = ["erb"]
|
||||||
|
autoclose_before = ">})"
|
||||||
|
brackets = [
|
||||||
|
{ start = "<", end = ">", close = true, newline = true },
|
||||||
|
]
|
||||||
|
block_comment = ["<%#", "%>"]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
12
crates/zed2/src/languages/erb/highlights.scm
Normal file
12
crates/zed2/src/languages/erb/highlights.scm
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
(comment_directive) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
"<%#"
|
||||||
|
"<%"
|
||||||
|
"<%="
|
||||||
|
"<%_"
|
||||||
|
"<%-"
|
||||||
|
"%>"
|
||||||
|
"-%>"
|
||||||
|
"_%>"
|
||||||
|
] @keyword
|
7
crates/zed2/src/languages/erb/injections.scm
Normal file
7
crates/zed2/src/languages/erb/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
((code) @content
|
||||||
|
(#set! "language" "ruby")
|
||||||
|
(#set! "combined"))
|
||||||
|
|
||||||
|
((content) @content
|
||||||
|
(#set! "language" "html")
|
||||||
|
(#set! "combined"))
|
9
crates/zed2/src/languages/glsl/config.toml
Normal file
9
crates/zed2/src/languages/glsl/config.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name = "GLSL"
|
||||||
|
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
|
||||||
|
line_comment = "// "
|
||||||
|
block_comment = ["/* ", " */"]
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
]
|
118
crates/zed2/src/languages/glsl/highlights.scm
Normal file
118
crates/zed2/src/languages/glsl/highlights.scm
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
"break" @keyword
|
||||||
|
"case" @keyword
|
||||||
|
"const" @keyword
|
||||||
|
"continue" @keyword
|
||||||
|
"default" @keyword
|
||||||
|
"do" @keyword
|
||||||
|
"else" @keyword
|
||||||
|
"enum" @keyword
|
||||||
|
"extern" @keyword
|
||||||
|
"for" @keyword
|
||||||
|
"if" @keyword
|
||||||
|
"inline" @keyword
|
||||||
|
"return" @keyword
|
||||||
|
"sizeof" @keyword
|
||||||
|
"static" @keyword
|
||||||
|
"struct" @keyword
|
||||||
|
"switch" @keyword
|
||||||
|
"typedef" @keyword
|
||||||
|
"union" @keyword
|
||||||
|
"volatile" @keyword
|
||||||
|
"while" @keyword
|
||||||
|
|
||||||
|
"#define" @keyword
|
||||||
|
"#elif" @keyword
|
||||||
|
"#else" @keyword
|
||||||
|
"#endif" @keyword
|
||||||
|
"#if" @keyword
|
||||||
|
"#ifdef" @keyword
|
||||||
|
"#ifndef" @keyword
|
||||||
|
"#include" @keyword
|
||||||
|
(preproc_directive) @keyword
|
||||||
|
|
||||||
|
"--" @operator
|
||||||
|
"-" @operator
|
||||||
|
"-=" @operator
|
||||||
|
"->" @operator
|
||||||
|
"=" @operator
|
||||||
|
"!=" @operator
|
||||||
|
"*" @operator
|
||||||
|
"&" @operator
|
||||||
|
"&&" @operator
|
||||||
|
"+" @operator
|
||||||
|
"++" @operator
|
||||||
|
"+=" @operator
|
||||||
|
"<" @operator
|
||||||
|
"==" @operator
|
||||||
|
">" @operator
|
||||||
|
"||" @operator
|
||||||
|
|
||||||
|
"." @delimiter
|
||||||
|
";" @delimiter
|
||||||
|
|
||||||
|
(string_literal) @string
|
||||||
|
(system_lib_string) @string
|
||||||
|
|
||||||
|
(null) @constant
|
||||||
|
(number_literal) @number
|
||||||
|
(char_literal) @number
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @function)
|
||||||
|
(call_expression
|
||||||
|
function: (field_expression
|
||||||
|
field: (field_identifier) @function))
|
||||||
|
(function_declarator
|
||||||
|
declarator: (identifier) @function)
|
||||||
|
(preproc_function_def
|
||||||
|
name: (identifier) @function.special)
|
||||||
|
|
||||||
|
(field_identifier) @property
|
||||||
|
(statement_identifier) @label
|
||||||
|
(type_identifier) @type
|
||||||
|
(primitive_type) @type
|
||||||
|
(sized_type_specifier) @type
|
||||||
|
|
||||||
|
((identifier) @constant
|
||||||
|
(#match? @constant "^[A-Z][A-Z\\d_]*$"))
|
||||||
|
|
||||||
|
(identifier) @variable
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
; inherits: c
|
||||||
|
|
||||||
|
[
|
||||||
|
"in"
|
||||||
|
"out"
|
||||||
|
"inout"
|
||||||
|
"uniform"
|
||||||
|
"shared"
|
||||||
|
"layout"
|
||||||
|
"attribute"
|
||||||
|
"varying"
|
||||||
|
"buffer"
|
||||||
|
"coherent"
|
||||||
|
"readonly"
|
||||||
|
"writeonly"
|
||||||
|
"precision"
|
||||||
|
"highp"
|
||||||
|
"mediump"
|
||||||
|
"lowp"
|
||||||
|
"centroid"
|
||||||
|
"sample"
|
||||||
|
"patch"
|
||||||
|
"smooth"
|
||||||
|
"flat"
|
||||||
|
"noperspective"
|
||||||
|
"invariant"
|
||||||
|
"precise"
|
||||||
|
] @type.qualifier
|
||||||
|
|
||||||
|
"subroutine" @keyword.function
|
||||||
|
|
||||||
|
(extension_storage_class) @storageclass
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @variable.builtin
|
||||||
|
(#match? @variable.builtin "^gl_")
|
||||||
|
)
|
464
crates/zed2/src/languages/go.rs
Normal file
464
crates/zed2/src/languages/go.rs
Normal file
|
@ -0,0 +1,464 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use gpui2::{AsyncAppContext, Task};
|
||||||
|
pub use language2::*;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
use regex::Regex;
|
||||||
|
use smol::{fs, process};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
ops::Range,
|
||||||
|
path::PathBuf,
|
||||||
|
str,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering::SeqCst},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use util::{fs::remove_matching, github::latest_github_release, ResultExt};
|
||||||
|
|
||||||
|
fn server_binary_arguments() -> Vec<OsString> {
|
||||||
|
vec!["-mode=stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct GoLspAdapter;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl super::LspAdapter for GoLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("gopls".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"gopls"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let release = latest_github_release("golang/tools", false, delegate.http_client()).await?;
|
||||||
|
let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
|
||||||
|
if version.is_none() {
|
||||||
|
log::warn!(
|
||||||
|
"couldn't infer gopls version from github release name '{}'",
|
||||||
|
release.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(Box::new(version) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn will_fetch_server(
|
||||||
|
&self,
|
||||||
|
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
const NOTIFICATION_MESSAGE: &str =
|
||||||
|
"Could not install the Go language server `gopls`, because `go` was not found.";
|
||||||
|
|
||||||
|
let delegate = delegate.clone();
|
||||||
|
Some(cx.spawn(|cx| async move {
|
||||||
|
let install_output = process::Command::new("go").args(["version"]).output().await;
|
||||||
|
if install_output.is_err() {
|
||||||
|
if DID_SHOW_NOTIFICATION
|
||||||
|
.compare_exchange(false, true, SeqCst, SeqCst)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
cx.update(|cx| {
|
||||||
|
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
return Err(anyhow!("cannot install gopls"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<Option<String>>().unwrap();
|
||||||
|
let this = *self;
|
||||||
|
|
||||||
|
if let Some(version) = *version {
|
||||||
|
let binary_path = container_dir.join(&format!("gopls_{version}"));
|
||||||
|
if let Ok(metadata) = fs::metadata(&binary_path).await {
|
||||||
|
if metadata.is_file() {
|
||||||
|
remove_matching(&container_dir, |entry| {
|
||||||
|
entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return Ok(LanguageServerBinary {
|
||||||
|
path: binary_path.to_path_buf(),
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(path) = this
|
||||||
|
.cached_server_binary(container_dir.clone(), delegate)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let gobin_dir = container_dir.join("gobin");
|
||||||
|
fs::create_dir_all(&gobin_dir).await?;
|
||||||
|
let install_output = process::Command::new("go")
|
||||||
|
.env("GO111MODULE", "on")
|
||||||
|
.env("GOBIN", &gobin_dir)
|
||||||
|
.args(["install", "golang.org/x/tools/gopls@latest"])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
if !install_output.status.success() {
|
||||||
|
Err(anyhow!("failed to install gopls. Is go installed?"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let installed_binary_path = gobin_dir.join("gopls");
|
||||||
|
let version_output = process::Command::new(&installed_binary_path)
|
||||||
|
.arg("version")
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?;
|
||||||
|
let version_stdout = str::from_utf8(&version_output.stdout)
|
||||||
|
.map_err(|_| anyhow!("gopls version produced invalid utf8"))?;
|
||||||
|
let version = GOPLS_VERSION_REGEX
|
||||||
|
.find(version_stdout)
|
||||||
|
.ok_or_else(|| anyhow!("failed to parse gopls version output"))?
|
||||||
|
.as_str();
|
||||||
|
let binary_path = container_dir.join(&format!("gopls_{version}"));
|
||||||
|
fs::rename(&installed_binary_path, &binary_path).await?;
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path.to_path_buf(),
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir)
|
||||||
|
.await
|
||||||
|
.map(|mut binary| {
|
||||||
|
binary.arguments = vec!["--help".into()];
|
||||||
|
binary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
completion: &lsp2::CompletionItem,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
let label = &completion.label;
|
||||||
|
|
||||||
|
// Gopls returns nested fields and methods as completions.
|
||||||
|
// To syntax highlight these, combine their final component
|
||||||
|
// with their detail.
|
||||||
|
let name_offset = label.rfind('.').unwrap_or(0);
|
||||||
|
|
||||||
|
match completion.kind.zip(completion.detail.as_ref()) {
|
||||||
|
Some((lsp2::CompletionItemKind::MODULE, detail)) => {
|
||||||
|
let text = format!("{label} {detail}");
|
||||||
|
let source = Rope::from(format!("import {text}").as_str());
|
||||||
|
let runs = language.highlight_text(&source, 7..7 + text.len());
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE,
|
||||||
|
detail,
|
||||||
|
)) => {
|
||||||
|
let text = format!("{label} {detail}");
|
||||||
|
let source =
|
||||||
|
Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
|
||||||
|
let runs = adjust_runs(
|
||||||
|
name_offset,
|
||||||
|
language.highlight_text(&source, 4..4 + text.len()),
|
||||||
|
);
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((lsp2::CompletionItemKind::STRUCT, _)) => {
|
||||||
|
let text = format!("{label} struct {{}}");
|
||||||
|
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||||
|
let runs = adjust_runs(
|
||||||
|
name_offset,
|
||||||
|
language.highlight_text(&source, 5..5 + text.len()),
|
||||||
|
);
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((lsp2::CompletionItemKind::INTERFACE, _)) => {
|
||||||
|
let text = format!("{label} interface {{}}");
|
||||||
|
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||||
|
let runs = adjust_runs(
|
||||||
|
name_offset,
|
||||||
|
language.highlight_text(&source, 5..5 + text.len()),
|
||||||
|
);
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((lsp2::CompletionItemKind::FIELD, detail)) => {
|
||||||
|
let text = format!("{label} {detail}");
|
||||||
|
let source =
|
||||||
|
Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
|
||||||
|
let runs = adjust_runs(
|
||||||
|
name_offset,
|
||||||
|
language.highlight_text(&source, 16..16 + text.len()),
|
||||||
|
);
|
||||||
|
return Some(CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD,
|
||||||
|
detail,
|
||||||
|
)) => {
|
||||||
|
if let Some(signature) = detail.strip_prefix("func") {
|
||||||
|
let text = format!("{label}{signature}");
|
||||||
|
let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
|
||||||
|
let runs = adjust_runs(
|
||||||
|
name_offset,
|
||||||
|
language.highlight_text(&source, 5..5 + text.len()),
|
||||||
|
);
|
||||||
|
return Some(CodeLabel {
|
||||||
|
filter_range: 0..label.len(),
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_symbol(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
kind: lsp2::SymbolKind,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
) -> Option<CodeLabel> {
|
||||||
|
let (text, filter_range, display_range) = match kind {
|
||||||
|
lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
|
||||||
|
let text = format!("func {} () {{}}", name);
|
||||||
|
let filter_range = 5..5 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::STRUCT => {
|
||||||
|
let text = format!("type {} struct {{}}", name);
|
||||||
|
let filter_range = 5..5 + name.len();
|
||||||
|
let display_range = 0..text.len();
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::INTERFACE => {
|
||||||
|
let text = format!("type {} interface {{}}", name);
|
||||||
|
let filter_range = 5..5 + name.len();
|
||||||
|
let display_range = 0..text.len();
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::CLASS => {
|
||||||
|
let text = format!("type {} T", name);
|
||||||
|
let filter_range = 5..5 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::CONSTANT => {
|
||||||
|
let text = format!("const {} = nil", name);
|
||||||
|
let filter_range = 6..6 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::VARIABLE => {
|
||||||
|
let text = format!("var {} = nil", name);
|
||||||
|
let filter_range = 4..4 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
lsp2::SymbolKind::MODULE => {
|
||||||
|
let text = format!("package {}", name);
|
||||||
|
let filter_range = 8..8 + name.len();
|
||||||
|
let display_range = 0..filter_range.end;
|
||||||
|
(text, filter_range, display_range)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(CodeLabel {
|
||||||
|
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||||
|
text: text[display_range].to_string(),
|
||||||
|
filter_range,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_binary_path = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_file()
|
||||||
|
&& entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.map_or(false, |name| name.starts_with("gopls_"))
|
||||||
|
{
|
||||||
|
last_binary_path = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = last_binary_path {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path,
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no cached binary"))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_runs(
|
||||||
|
delta: usize,
|
||||||
|
mut runs: Vec<(Range<usize>, HighlightId)>,
|
||||||
|
) -> Vec<(Range<usize>, HighlightId)> {
|
||||||
|
for (range, _) in &mut runs {
|
||||||
|
range.start += delta;
|
||||||
|
range.end += delta;
|
||||||
|
}
|
||||||
|
runs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::languages::language;
|
||||||
|
use gpui2::Hsla;
|
||||||
|
use theme2::SyntaxTheme;
|
||||||
|
|
||||||
|
#[gpui2::test]
|
||||||
|
async fn test_go_label_for_completion() {
|
||||||
|
let language = language(
|
||||||
|
"go",
|
||||||
|
tree_sitter_go::language(),
|
||||||
|
Some(Arc::new(GoLspAdapter)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let theme = SyntaxTheme::new_test([
|
||||||
|
("type", Hsla::default()),
|
||||||
|
("keyword", Hsla::default()),
|
||||||
|
("function", Hsla::default()),
|
||||||
|
("number", Hsla::default()),
|
||||||
|
("property", Hsla::default()),
|
||||||
|
]);
|
||||||
|
language.set_theme(&theme);
|
||||||
|
|
||||||
|
let grammar = language.grammar().unwrap();
|
||||||
|
let highlight_function = grammar.highlight_id_for_name("function").unwrap();
|
||||||
|
let highlight_type = grammar.highlight_id_for_name("type").unwrap();
|
||||||
|
let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
|
||||||
|
let highlight_number = grammar.highlight_id_for_name("number").unwrap();
|
||||||
|
let highlight_field = grammar.highlight_id_for_name("property").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
language
|
||||||
|
.label_for_completion(&lsp2::CompletionItem {
|
||||||
|
kind: Some(lsp2::CompletionItemKind::FUNCTION),
|
||||||
|
label: "Hello".to_string(),
|
||||||
|
detail: Some("func(a B) c.D".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await,
|
||||||
|
Some(CodeLabel {
|
||||||
|
text: "Hello(a B) c.D".to_string(),
|
||||||
|
filter_range: 0..5,
|
||||||
|
runs: vec![
|
||||||
|
(0..5, highlight_function),
|
||||||
|
(8..9, highlight_type),
|
||||||
|
(13..14, highlight_type),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nested methods
|
||||||
|
assert_eq!(
|
||||||
|
language
|
||||||
|
.label_for_completion(&lsp2::CompletionItem {
|
||||||
|
kind: Some(lsp2::CompletionItemKind::METHOD),
|
||||||
|
label: "one.two.Three".to_string(),
|
||||||
|
detail: Some("func() [3]interface{}".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await,
|
||||||
|
Some(CodeLabel {
|
||||||
|
text: "one.two.Three() [3]interface{}".to_string(),
|
||||||
|
filter_range: 0..13,
|
||||||
|
runs: vec![
|
||||||
|
(8..13, highlight_function),
|
||||||
|
(17..18, highlight_number),
|
||||||
|
(19..28, highlight_keyword),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nested fields
|
||||||
|
assert_eq!(
|
||||||
|
language
|
||||||
|
.label_for_completion(&lsp2::CompletionItem {
|
||||||
|
kind: Some(lsp2::CompletionItemKind::FIELD),
|
||||||
|
label: "two.Three".to_string(),
|
||||||
|
detail: Some("a.Bcd".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await,
|
||||||
|
Some(CodeLabel {
|
||||||
|
text: "two.Three a.Bcd".to_string(),
|
||||||
|
filter_range: 0..9,
|
||||||
|
runs: vec![(4..9, highlight_field), (12..15, highlight_type)],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
crates/zed2/src/languages/go/brackets.scm
Normal file
3
crates/zed2/src/languages/go/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("\"" @open "\"" @close)
|
12
crates/zed2/src/languages/go/config.toml
Normal file
12
crates/zed2/src/languages/go/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name = "Go"
|
||||||
|
path_suffixes = ["go"]
|
||||||
|
line_comment = "// "
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
]
|
24
crates/zed2/src/languages/go/embedding.scm
Normal file
24
crates/zed2/src/languages/go/embedding.scm
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(type_declaration
|
||||||
|
(type_spec
|
||||||
|
name: (_) @name)
|
||||||
|
) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(function_declaration
|
||||||
|
name: (_) @name
|
||||||
|
) @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(method_declaration
|
||||||
|
name: (_) @name
|
||||||
|
) @item
|
||||||
|
)
|
107
crates/zed2/src/languages/go/highlights.scm
Normal file
107
crates/zed2/src/languages/go/highlights.scm
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
(identifier) @variable
|
||||||
|
(type_identifier) @type
|
||||||
|
(field_identifier) @property
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @function)
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (selector_expression
|
||||||
|
field: (field_identifier) @function.method))
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
name: (identifier) @function)
|
||||||
|
|
||||||
|
(method_declaration
|
||||||
|
name: (field_identifier) @function.method)
|
||||||
|
|
||||||
|
[
|
||||||
|
"--"
|
||||||
|
"-"
|
||||||
|
"-="
|
||||||
|
":="
|
||||||
|
"!"
|
||||||
|
"!="
|
||||||
|
"..."
|
||||||
|
"*"
|
||||||
|
"*"
|
||||||
|
"*="
|
||||||
|
"/"
|
||||||
|
"/="
|
||||||
|
"&"
|
||||||
|
"&&"
|
||||||
|
"&="
|
||||||
|
"%"
|
||||||
|
"%="
|
||||||
|
"^"
|
||||||
|
"^="
|
||||||
|
"+"
|
||||||
|
"++"
|
||||||
|
"+="
|
||||||
|
"<-"
|
||||||
|
"<"
|
||||||
|
"<<"
|
||||||
|
"<<="
|
||||||
|
"<="
|
||||||
|
"="
|
||||||
|
"=="
|
||||||
|
">"
|
||||||
|
">="
|
||||||
|
">>"
|
||||||
|
">>="
|
||||||
|
"|"
|
||||||
|
"|="
|
||||||
|
"||"
|
||||||
|
"~"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"break"
|
||||||
|
"case"
|
||||||
|
"chan"
|
||||||
|
"const"
|
||||||
|
"continue"
|
||||||
|
"default"
|
||||||
|
"defer"
|
||||||
|
"else"
|
||||||
|
"fallthrough"
|
||||||
|
"for"
|
||||||
|
"func"
|
||||||
|
"go"
|
||||||
|
"goto"
|
||||||
|
"if"
|
||||||
|
"import"
|
||||||
|
"interface"
|
||||||
|
"map"
|
||||||
|
"package"
|
||||||
|
"range"
|
||||||
|
"return"
|
||||||
|
"select"
|
||||||
|
"struct"
|
||||||
|
"switch"
|
||||||
|
"type"
|
||||||
|
"var"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
(interpreted_string_literal)
|
||||||
|
(raw_string_literal)
|
||||||
|
(rune_literal)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(escape_sequence) @escape
|
||||||
|
|
||||||
|
[
|
||||||
|
(int_literal)
|
||||||
|
(float_literal)
|
||||||
|
(imaginary_literal)
|
||||||
|
] @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
(nil)
|
||||||
|
(iota)
|
||||||
|
] @constant.builtin
|
||||||
|
|
||||||
|
(comment) @comment
|
9
crates/zed2/src/languages/go/indents.scm
Normal file
9
crates/zed2/src/languages/go/indents.scm
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[
|
||||||
|
(assignment_statement)
|
||||||
|
(call_expression)
|
||||||
|
(selector_expression)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
(_ "[" "]" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent
|
43
crates/zed2/src/languages/go/outline.scm
Normal file
43
crates/zed2/src/languages/go/outline.scm
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
(type_declaration
|
||||||
|
"type" @context
|
||||||
|
(type_spec
|
||||||
|
name: (_) @name)) @item
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
"func" @context
|
||||||
|
name: (identifier) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(method_declaration
|
||||||
|
"func" @context
|
||||||
|
receiver: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
(parameter_declaration
|
||||||
|
type: (_) @context)
|
||||||
|
")" @context)
|
||||||
|
name: (field_identifier) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(const_declaration
|
||||||
|
"const" @context
|
||||||
|
(const_spec
|
||||||
|
name: (identifier) @name) @item)
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(var_declaration
|
||||||
|
"var" @context
|
||||||
|
(var_spec
|
||||||
|
name: (identifier) @name) @item))
|
||||||
|
|
||||||
|
(method_spec
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (parameter_list
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(field_declaration
|
||||||
|
name: (_) @name) @item
|
6
crates/zed2/src/languages/go/overrides.scm
Normal file
6
crates/zed2/src/languages/go/overrides.scm
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(comment) @comment
|
||||||
|
[
|
||||||
|
(interpreted_string_literal)
|
||||||
|
(raw_string_literal)
|
||||||
|
(rune_literal)
|
||||||
|
] @string
|
12
crates/zed2/src/languages/heex/config.toml
Normal file
12
crates/zed2/src/languages/heex/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name = "HEEX"
|
||||||
|
path_suffixes = ["heex"]
|
||||||
|
autoclose_before = ">})"
|
||||||
|
brackets = [
|
||||||
|
{ start = "<", end = ">", close = true, newline = true },
|
||||||
|
]
|
||||||
|
block_comment = ["<%!-- ", " --%>"]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||||
|
|
||||||
|
[overrides.string]
|
||||||
|
word_characters = ["-"]
|
||||||
|
opt_into_language_servers = ["tailwindcss-language-server"]
|
57
crates/zed2/src/languages/heex/highlights.scm
Normal file
57
crates/zed2/src/languages/heex/highlights.scm
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
; HEEx delimiters
|
||||||
|
[
|
||||||
|
"/>"
|
||||||
|
"<!"
|
||||||
|
"<"
|
||||||
|
"</"
|
||||||
|
"</:"
|
||||||
|
"<:"
|
||||||
|
">"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
"<%!--"
|
||||||
|
"<%"
|
||||||
|
"<%#"
|
||||||
|
"<%%="
|
||||||
|
"<%="
|
||||||
|
"%>"
|
||||||
|
"--%>"
|
||||||
|
"-->"
|
||||||
|
"<!--"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
; HEEx operators are highlighted as such
|
||||||
|
"=" @operator
|
||||||
|
|
||||||
|
; HEEx inherits the DOCTYPE tag from HTML
|
||||||
|
(doctype) @constant
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
; HEEx tags and slots are highlighted as HTML
|
||||||
|
[
|
||||||
|
(tag_name)
|
||||||
|
(slot_name)
|
||||||
|
] @tag
|
||||||
|
|
||||||
|
; HEEx attributes are highlighted as HTML attributes
|
||||||
|
(attribute_name) @attribute
|
||||||
|
|
||||||
|
; HEEx special attributes are highlighted as keywords
|
||||||
|
(special_attribute_name) @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
(attribute_value)
|
||||||
|
(quoted_attribute_value)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
; HEEx components are highlighted as Elixir modules and functions
|
||||||
|
(component_name
|
||||||
|
[
|
||||||
|
(module) @module
|
||||||
|
(function) @function
|
||||||
|
"." @punctuation.delimiter
|
||||||
|
])
|
13
crates/zed2/src/languages/heex/injections.scm
Normal file
13
crates/zed2/src/languages/heex/injections.scm
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(
|
||||||
|
(directive
|
||||||
|
[
|
||||||
|
(partial_expression_value)
|
||||||
|
(expression_value)
|
||||||
|
(ending_expression_value)
|
||||||
|
] @content)
|
||||||
|
(#set! language "elixir")
|
||||||
|
(#set! combined)
|
||||||
|
)
|
||||||
|
|
||||||
|
((expression (expression_value) @content)
|
||||||
|
(#set! language "elixir"))
|
4
crates/zed2/src/languages/heex/overrides.scm
Normal file
4
crates/zed2/src/languages/heex/overrides.scm
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
(attribute_value)
|
||||||
|
(quoted_attribute_value)
|
||||||
|
] @string
|
130
crates/zed2/src/languages/html.rs
Normal file
130
crates/zed2/src/languages/html.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde_json::json;
|
||||||
|
use smol::fs;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::OsString,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
const SERVER_PATH: &'static str =
|
||||||
|
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
||||||
|
|
||||||
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HtmlLspAdapter {
|
||||||
|
node: Arc<dyn NodeRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HtmlLspAdapter {
|
||||||
|
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||||
|
HtmlLspAdapter { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for HtmlLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("vscode-html-language-server".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"html"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("vscode-langservers-extracted")
|
||||||
|
.await?,
|
||||||
|
) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
&container_dir,
|
||||||
|
&[("vscode-langservers-extracted", version.as_str())],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
Some(json!({
|
||||||
|
"provideFormatter": true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
node: &dyn NodeRuntime,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_version_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_version_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
last_version_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
2
crates/zed2/src/languages/html/brackets.scm
Normal file
2
crates/zed2/src/languages/html/brackets.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
("<" @open ">" @close)
|
||||||
|
("\"" @open "\"" @close)
|
14
crates/zed2/src/languages/html/config.toml
Normal file
14
crates/zed2/src/languages/html/config.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
name = "HTML"
|
||||||
|
path_suffixes = ["html"]
|
||||||
|
autoclose_before = ">})"
|
||||||
|
block_comment = ["<!-- ", " -->"]
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "<", end = ">", close = true, newline = true, not_in = ["comment", "string"] },
|
||||||
|
{ start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
]
|
||||||
|
word_characters = ["-"]
|
||||||
|
prettier_parser_name = "html"
|
15
crates/zed2/src/languages/html/highlights.scm
Normal file
15
crates/zed2/src/languages/html/highlights.scm
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
(tag_name) @keyword
|
||||||
|
(erroneous_end_tag_name) @keyword
|
||||||
|
(doctype) @constant
|
||||||
|
(attribute_name) @property
|
||||||
|
(attribute_value) @string
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
"=" @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"<"
|
||||||
|
">"
|
||||||
|
"</"
|
||||||
|
"/>"
|
||||||
|
] @punctuation.bracket
|
6
crates/zed2/src/languages/html/indents.scm
Normal file
6
crates/zed2/src/languages/html/indents.scm
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(start_tag ">" @end) @indent
|
||||||
|
(self_closing_tag "/>" @end) @indent
|
||||||
|
|
||||||
|
(element
|
||||||
|
(start_tag) @start
|
||||||
|
(end_tag)? @end) @indent
|
7
crates/zed2/src/languages/html/injections.scm
Normal file
7
crates/zed2/src/languages/html/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(script_element
|
||||||
|
(raw_text) @content
|
||||||
|
(#set! "language" "javascript"))
|
||||||
|
|
||||||
|
(style_element
|
||||||
|
(raw_text) @content
|
||||||
|
(#set! "language" "css"))
|
0
crates/zed2/src/languages/html/outline.scm
Normal file
0
crates/zed2/src/languages/html/outline.scm
Normal file
2
crates/zed2/src/languages/html/overrides.scm
Normal file
2
crates/zed2/src/languages/html/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(comment) @comment
|
||||||
|
(quoted_attribute_value) @string
|
5
crates/zed2/src/languages/javascript/brackets.scm
Normal file
5
crates/zed2/src/languages/javascript/brackets.scm
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("<" @open ">" @close)
|
||||||
|
("\"" @open "\"" @close)
|
26
crates/zed2/src/languages/javascript/config.toml
Normal file
26
crates/zed2/src/languages/javascript/config.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name = "JavaScript"
|
||||||
|
path_suffixes = ["js", "jsx", "mjs", "cjs"]
|
||||||
|
first_line_pattern = '^#!.*\bnode\b'
|
||||||
|
line_comment = "// "
|
||||||
|
autoclose_before = ";:.,=}])>"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "<", end = ">", close = false, newline = true, not_in = ["comment", "string"] },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "`", end = "`", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
|
]
|
||||||
|
word_characters = ["$", "#"]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||||
|
prettier_parser_name = "babel"
|
||||||
|
|
||||||
|
[overrides.element]
|
||||||
|
line_comment = { remove = true }
|
||||||
|
block_comment = ["{/* ", " */}"]
|
||||||
|
|
||||||
|
[overrides.string]
|
||||||
|
word_characters = ["-"]
|
||||||
|
opt_into_language_servers = ["tailwindcss-language-server"]
|
0
crates/zed2/src/languages/javascript/contexts.scm
Normal file
0
crates/zed2/src/languages/javascript/contexts.scm
Normal file
71
crates/zed2/src/languages/javascript/embedding.scm
Normal file
71
crates/zed2/src/languages/javascript/embedding.scm
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(export_statement
|
||||||
|
(function_declaration
|
||||||
|
"async"? @name
|
||||||
|
"function" @name
|
||||||
|
name: (_) @name))
|
||||||
|
(function_declaration
|
||||||
|
"async"? @name
|
||||||
|
"function" @name
|
||||||
|
name: (_) @name)
|
||||||
|
] @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(export_statement
|
||||||
|
(class_declaration
|
||||||
|
"class" @name
|
||||||
|
name: (_) @name))
|
||||||
|
(class_declaration
|
||||||
|
"class" @name
|
||||||
|
name: (_) @name)
|
||||||
|
] @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(export_statement
|
||||||
|
(interface_declaration
|
||||||
|
"interface" @name
|
||||||
|
name: (_) @name))
|
||||||
|
(interface_declaration
|
||||||
|
"interface" @name
|
||||||
|
name: (_) @name)
|
||||||
|
] @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(export_statement
|
||||||
|
(enum_declaration
|
||||||
|
"enum" @name
|
||||||
|
name: (_) @name))
|
||||||
|
(enum_declaration
|
||||||
|
"enum" @name
|
||||||
|
name: (_) @name)
|
||||||
|
] @item
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @context
|
||||||
|
.
|
||||||
|
(method_definition
|
||||||
|
[
|
||||||
|
"get"
|
||||||
|
"set"
|
||||||
|
"async"
|
||||||
|
"*"
|
||||||
|
"static"
|
||||||
|
]* @name
|
||||||
|
name: (_) @name) @item
|
||||||
|
)
|
217
crates/zed2/src/languages/javascript/highlights.scm
Normal file
217
crates/zed2/src/languages/javascript/highlights.scm
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
; Variables
|
||||||
|
|
||||||
|
(identifier) @variable
|
||||||
|
|
||||||
|
; Properties
|
||||||
|
|
||||||
|
(property_identifier) @property
|
||||||
|
|
||||||
|
; Function and method calls
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @function)
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (member_expression
|
||||||
|
property: (property_identifier) @function.method))
|
||||||
|
|
||||||
|
; Function and method definitions
|
||||||
|
|
||||||
|
(function
|
||||||
|
name: (identifier) @function)
|
||||||
|
(function_declaration
|
||||||
|
name: (identifier) @function)
|
||||||
|
(method_definition
|
||||||
|
name: (property_identifier) @function.method)
|
||||||
|
|
||||||
|
(pair
|
||||||
|
key: (property_identifier) @function.method
|
||||||
|
value: [(function) (arrow_function)])
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: (member_expression
|
||||||
|
property: (property_identifier) @function.method)
|
||||||
|
right: [(function) (arrow_function)])
|
||||||
|
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @function
|
||||||
|
value: [(function) (arrow_function)])
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: (identifier) @function
|
||||||
|
right: [(function) (arrow_function)])
|
||||||
|
|
||||||
|
; Special identifiers
|
||||||
|
|
||||||
|
((identifier) @type
|
||||||
|
(#match? @type "^[A-Z]"))
|
||||||
|
(type_identifier) @type
|
||||||
|
(predefined_type) @type.builtin
|
||||||
|
|
||||||
|
([
|
||||||
|
(identifier)
|
||||||
|
(shorthand_property_identifier)
|
||||||
|
(shorthand_property_identifier_pattern)
|
||||||
|
] @constant
|
||||||
|
(#match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
|
||||||
|
|
||||||
|
; Literals
|
||||||
|
|
||||||
|
(this) @variable.special
|
||||||
|
(super) @variable.special
|
||||||
|
|
||||||
|
[
|
||||||
|
(null)
|
||||||
|
(undefined)
|
||||||
|
] @constant.builtin
|
||||||
|
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
] @boolean
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(template_string)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(regex) @string.regex
|
||||||
|
(number) @number
|
||||||
|
|
||||||
|
; Tokens
|
||||||
|
|
||||||
|
[
|
||||||
|
";"
|
||||||
|
"?."
|
||||||
|
"."
|
||||||
|
","
|
||||||
|
":"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"-"
|
||||||
|
"--"
|
||||||
|
"-="
|
||||||
|
"+"
|
||||||
|
"++"
|
||||||
|
"+="
|
||||||
|
"*"
|
||||||
|
"*="
|
||||||
|
"**"
|
||||||
|
"**="
|
||||||
|
"/"
|
||||||
|
"/="
|
||||||
|
"%"
|
||||||
|
"%="
|
||||||
|
"<"
|
||||||
|
"<="
|
||||||
|
"<<"
|
||||||
|
"<<="
|
||||||
|
"="
|
||||||
|
"=="
|
||||||
|
"==="
|
||||||
|
"!"
|
||||||
|
"!="
|
||||||
|
"!=="
|
||||||
|
"=>"
|
||||||
|
">"
|
||||||
|
">="
|
||||||
|
">>"
|
||||||
|
">>="
|
||||||
|
">>>"
|
||||||
|
">>>="
|
||||||
|
"~"
|
||||||
|
"^"
|
||||||
|
"&"
|
||||||
|
"|"
|
||||||
|
"^="
|
||||||
|
"&="
|
||||||
|
"|="
|
||||||
|
"&&"
|
||||||
|
"||"
|
||||||
|
"??"
|
||||||
|
"&&="
|
||||||
|
"||="
|
||||||
|
"??="
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
"as"
|
||||||
|
"async"
|
||||||
|
"await"
|
||||||
|
"break"
|
||||||
|
"case"
|
||||||
|
"catch"
|
||||||
|
"class"
|
||||||
|
"const"
|
||||||
|
"continue"
|
||||||
|
"debugger"
|
||||||
|
"default"
|
||||||
|
"delete"
|
||||||
|
"do"
|
||||||
|
"else"
|
||||||
|
"export"
|
||||||
|
"extends"
|
||||||
|
"finally"
|
||||||
|
"for"
|
||||||
|
"from"
|
||||||
|
"function"
|
||||||
|
"get"
|
||||||
|
"if"
|
||||||
|
"import"
|
||||||
|
"in"
|
||||||
|
"instanceof"
|
||||||
|
"let"
|
||||||
|
"new"
|
||||||
|
"of"
|
||||||
|
"return"
|
||||||
|
"set"
|
||||||
|
"static"
|
||||||
|
"switch"
|
||||||
|
"target"
|
||||||
|
"throw"
|
||||||
|
"try"
|
||||||
|
"typeof"
|
||||||
|
"var"
|
||||||
|
"void"
|
||||||
|
"while"
|
||||||
|
"with"
|
||||||
|
"yield"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(template_substitution
|
||||||
|
"${" @punctuation.special
|
||||||
|
"}" @punctuation.special) @embedded
|
||||||
|
|
||||||
|
(type_arguments
|
||||||
|
"<" @punctuation.bracket
|
||||||
|
">" @punctuation.bracket)
|
||||||
|
|
||||||
|
; Keywords
|
||||||
|
|
||||||
|
[ "abstract"
|
||||||
|
"declare"
|
||||||
|
"enum"
|
||||||
|
"export"
|
||||||
|
"implements"
|
||||||
|
"interface"
|
||||||
|
"keyof"
|
||||||
|
"namespace"
|
||||||
|
"private"
|
||||||
|
"protected"
|
||||||
|
"public"
|
||||||
|
"type"
|
||||||
|
"readonly"
|
||||||
|
"override"
|
||||||
|
] @keyword
|
15
crates/zed2/src/languages/javascript/indents.scm
Normal file
15
crates/zed2/src/languages/javascript/indents.scm
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[
|
||||||
|
(call_expression)
|
||||||
|
(assignment_expression)
|
||||||
|
(member_expression)
|
||||||
|
(lexical_declaration)
|
||||||
|
(variable_declaration)
|
||||||
|
(assignment_expression)
|
||||||
|
(if_statement)
|
||||||
|
(for_statement)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
(_ "[" "]" @end) @indent
|
||||||
|
(_ "<" ">" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent
|
62
crates/zed2/src/languages/javascript/outline.scm
Normal file
62
crates/zed2/src/languages/javascript/outline.scm
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
(internal_module
|
||||||
|
"namespace" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(enum_declaration
|
||||||
|
"enum" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
"async"? @context
|
||||||
|
"function" @context
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (formal_parameters
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(interface_declaration
|
||||||
|
"interface" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(program
|
||||||
|
(export_statement
|
||||||
|
(lexical_declaration
|
||||||
|
["let" "const"] @context
|
||||||
|
(variable_declarator
|
||||||
|
name: (_) @name) @item)))
|
||||||
|
|
||||||
|
(program
|
||||||
|
(lexical_declaration
|
||||||
|
["let" "const"] @context
|
||||||
|
(variable_declarator
|
||||||
|
name: (_) @name) @item))
|
||||||
|
|
||||||
|
(class_declaration
|
||||||
|
"class" @context
|
||||||
|
name: (_) @name) @item
|
||||||
|
|
||||||
|
(method_definition
|
||||||
|
[
|
||||||
|
"get"
|
||||||
|
"set"
|
||||||
|
"async"
|
||||||
|
"*"
|
||||||
|
"readonly"
|
||||||
|
"static"
|
||||||
|
(override_modifier)
|
||||||
|
(accessibility_modifier)
|
||||||
|
]* @context
|
||||||
|
name: (_) @name
|
||||||
|
parameters: (formal_parameters
|
||||||
|
"(" @context
|
||||||
|
")" @context)) @item
|
||||||
|
|
||||||
|
(public_field_definition
|
||||||
|
[
|
||||||
|
"declare"
|
||||||
|
"readonly"
|
||||||
|
"abstract"
|
||||||
|
"static"
|
||||||
|
(accessibility_modifier)
|
||||||
|
]* @context
|
||||||
|
name: (_) @name) @item
|
13
crates/zed2/src/languages/javascript/overrides.scm
Normal file
13
crates/zed2/src/languages/javascript/overrides.scm
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(template_string)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
[
|
||||||
|
(jsx_element)
|
||||||
|
(jsx_fragment)
|
||||||
|
(jsx_self_closing_element)
|
||||||
|
(jsx_expression)
|
||||||
|
] @element
|
184
crates/zed2/src/languages/json.rs
Normal file
184
crates/zed2/src/languages/json.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use collections::HashMap;
|
||||||
|
use feature_flags2::FeatureFlagAppExt;
|
||||||
|
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||||
|
use gpui2::AppContext;
|
||||||
|
use language2::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde_json::json;
|
||||||
|
use settings2::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
|
||||||
|
use smol::fs;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::OsString,
|
||||||
|
future,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::{paths, ResultExt};
|
||||||
|
|
||||||
|
const SERVER_PATH: &'static str =
|
||||||
|
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
|
||||||
|
|
||||||
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JsonLspAdapter {
|
||||||
|
node: Arc<dyn NodeRuntime>,
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsonLspAdapter {
|
||||||
|
pub fn new(node: Arc<dyn NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
|
||||||
|
JsonLspAdapter { node, languages }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for JsonLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("json-language-server".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"json"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("vscode-json-languageserver")
|
||||||
|
.await?,
|
||||||
|
) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
&container_dir,
|
||||||
|
&[("vscode-json-languageserver", version.as_str())],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &*self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
Some(json!({
|
||||||
|
"provideFormatter": true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_configuration(
|
||||||
|
&self,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> BoxFuture<'static, serde_json::Value> {
|
||||||
|
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
||||||
|
let staff_mode = cx.is_staff();
|
||||||
|
let language_names = &self.languages.language_names();
|
||||||
|
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
||||||
|
&SettingsJsonSchemaParams {
|
||||||
|
language_names,
|
||||||
|
staff_mode,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
future::ready(serde_json::json!({
|
||||||
|
"json": {
|
||||||
|
"format": {
|
||||||
|
"enable": true,
|
||||||
|
},
|
||||||
|
"schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
schema_file_match(&paths::SETTINGS),
|
||||||
|
&*paths::LOCAL_SETTINGS_RELATIVE_PATH,
|
||||||
|
],
|
||||||
|
"schema": settings_schema,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [schema_file_match(&paths::KEYMAP)],
|
||||||
|
"schema": KeymapFile::generate_json_schema(&action_names),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn language_ids(&self) -> HashMap<String, String> {
|
||||||
|
[("JSON".into(), "jsonc".into())].into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
node: &dyn NodeRuntime,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_version_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_version_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
last_version_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_file_match(path: &Path) -> &Path {
|
||||||
|
path.strip_prefix(path.parent().unwrap().parent().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
}
|
3
crates/zed2/src/languages/json/brackets.scm
Normal file
3
crates/zed2/src/languages/json/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("\"" @open "\"" @close)
|
10
crates/zed2/src/languages/json/config.toml
Normal file
10
crates/zed2/src/languages/json/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name = "JSON"
|
||||||
|
path_suffixes = ["json"]
|
||||||
|
line_comment = "// "
|
||||||
|
autoclose_before = ",]}"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
]
|
||||||
|
prettier_parser_name = "json"
|
14
crates/zed2/src/languages/json/embedding.scm
Normal file
14
crates/zed2/src/languages/json/embedding.scm
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
; Only produce one embedding for the entire file.
|
||||||
|
(document) @item
|
||||||
|
|
||||||
|
; Collapse arrays, except for the first object.
|
||||||
|
(array
|
||||||
|
"[" @keep
|
||||||
|
.
|
||||||
|
(object)? @keep
|
||||||
|
"]" @keep) @collapse
|
||||||
|
|
||||||
|
; Collapse string values (but not keys).
|
||||||
|
(pair value: (string
|
||||||
|
"\"" @keep
|
||||||
|
"\"" @keep) @collapse)
|
21
crates/zed2/src/languages/json/highlights.scm
Normal file
21
crates/zed2/src/languages/json/highlights.scm
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
(string) @string
|
||||||
|
|
||||||
|
(pair
|
||||||
|
key: (string) @property)
|
||||||
|
|
||||||
|
(number) @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
(null)
|
||||||
|
] @constant
|
||||||
|
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
] @punctuation.bracket
|
2
crates/zed2/src/languages/json/indents.scm
Normal file
2
crates/zed2/src/languages/json/indents.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(array "]" @end) @indent
|
||||||
|
(object "}" @end) @indent
|
2
crates/zed2/src/languages/json/outline.scm
Normal file
2
crates/zed2/src/languages/json/outline.scm
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @name)) @item
|
1
crates/zed2/src/languages/json/overrides.scm
Normal file
1
crates/zed2/src/languages/json/overrides.scm
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(string) @string
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue