mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-04 05:46:48 +03:00
Merge branch 'zed2' into zed2-workspace
This commit is contained in:
commit
00c92ae407
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -10983,7 +10983,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"db2",
|
||||
"env_logger 0.9.3",
|
||||
"feature_flags",
|
||||
"feature_flags2",
|
||||
"fs2",
|
||||
"fsevent",
|
||||
"futures 0.3.28",
|
||||
@ -11000,7 +11000,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"lsp",
|
||||
"lsp2",
|
||||
"node_runtime",
|
||||
"num_cpus",
|
||||
"parking_lot 0.11.2",
|
||||
@ -11053,6 +11053,7 @@ dependencies = [
|
||||
"tree-sitter-svelte",
|
||||
"tree-sitter-toml",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-vue",
|
||||
"tree-sitter-yaml",
|
||||
"unindent",
|
||||
"url",
|
||||
|
@ -67,8 +67,8 @@ impl ActiveCall {
|
||||
incoming_call: watch::channel(),
|
||||
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(cx.weak_handle(), Self::handle_incoming_call),
|
||||
client.add_message_handler(cx.weak_handle(), Self::handle_call_canceled),
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
||||
],
|
||||
client,
|
||||
user_store,
|
||||
|
@ -122,9 +122,9 @@ impl UserStore {
|
||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
||||
let rpc_subscriptions = vec![
|
||||
client.add_message_handler(cx.weak_handle(), Self::handle_update_contacts),
|
||||
client.add_message_handler(cx.weak_handle(), 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_update_contacts),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
|
||||
];
|
||||
Self {
|
||||
users: Default::default(),
|
||||
|
@ -7,8 +7,8 @@ use async_tar::Archive;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui2::{
|
||||
AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Model, ModelContext, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use language2::{
|
||||
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.
|
||||
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
|
||||
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
|
||||
/// entity along with a `ModelContext` for the entity.
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
|
@ -29,14 +29,14 @@ impl Context for AsyncAppContext {
|
||||
Ok(lock.build_model(build_model))
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
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))
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Result<R> {
|
||||
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 derive_more::{Deref, DerefMut};
|
||||
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 {
|
||||
Some(Model {
|
||||
any_model: self.clone(),
|
||||
Ok(Model {
|
||||
any_model: self,
|
||||
entity_type: PhantomData,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,6 +243,14 @@ impl PartialEq 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)]
|
||||
pub struct Model<T> {
|
||||
#[deref]
|
||||
@ -253,6 +261,32 @@ pub struct Model<T> {
|
||||
|
||||
unsafe impl<T> Send 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> {
|
||||
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> {
|
||||
WeakModel {
|
||||
any_model: self.any_model.downgrade(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
// Delegate to the trait implementation to keep behavior in one place.
|
||||
// This method was included to improve method resolution in the presence of
|
||||
// the Model's deref
|
||||
Entity::downgrade(self)
|
||||
}
|
||||
|
||||
/// Convert this into a dynamically typed model.
|
||||
@ -294,7 +329,7 @@ impl<T: 'static> Model<T> {
|
||||
where
|
||||
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> {
|
||||
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> {
|
||||
/// Upgrade this weak model reference into a strong model reference
|
||||
pub fn upgrade(&self) -> Option<Model<T>> {
|
||||
Some(Model {
|
||||
any_model: self.any_model.upgrade()?,
|
||||
entity_type: self.entity_type,
|
||||
})
|
||||
// Delegate to the trait implementation to keep behavior in one place.
|
||||
Model::upgrade_from(self)
|
||||
}
|
||||
|
||||
/// Update the entity referenced by this model with the given function if
|
||||
@ -441,7 +475,7 @@ impl<T: 'static> WeakModel<T> {
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade()
|
||||
.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> {
|
||||
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::{
|
||||
AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, MainThread, Model,
|
||||
Reference, Subscription, Task, WeakModel,
|
||||
AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread,
|
||||
Model, Reference, Subscription, Task, WeakModel,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::FutureExt;
|
||||
@ -31,29 +31,32 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> Model<T> {
|
||||
self.weak_handle()
|
||||
self.weak_model()
|
||||
.upgrade()
|
||||
.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()
|
||||
}
|
||||
|
||||
pub fn observe<T2: 'static>(
|
||||
pub fn observe<T2, E>(
|
||||
&mut self,
|
||||
handle: &Model<T2>,
|
||||
mut on_notify: impl FnMut(&mut T, Model<T2>, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + Send,
|
||||
T2: 'static,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
let this = self.weak_handle();
|
||||
let handle = handle.downgrade();
|
||||
let this = self.weak_model();
|
||||
let entity_id = entity.entity_id();
|
||||
let handle = entity.downgrade();
|
||||
self.app.observers.insert(
|
||||
handle.entity_id,
|
||||
entity_id,
|
||||
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));
|
||||
true
|
||||
} 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,
|
||||
handle: &Model<E>,
|
||||
mut on_event: impl FnMut(&mut T, Model<E>, &E::Event, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + Send,
|
||||
T2: 'static + EventEmitter,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
let this = self.weak_handle();
|
||||
let handle = handle.downgrade();
|
||||
let this = self.weak_model();
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
self.app.event_listeners.insert(
|
||||
handle.entity_id,
|
||||
entity_id,
|
||||
Box::new(move |event, cx| {
|
||||
let event: &E::Event = event.downcast_ref().expect("invalid event type");
|
||||
if let Some((this, handle)) = this.upgrade().zip(handle.upgrade()) {
|
||||
let event: &T2::Event = event.downcast_ref().expect("invalid event type");
|
||||
if let Some((this, handle)) = this.upgrade().zip(E::upgrade_from(&entity)) {
|
||||
this.update(cx, |this, cx| on_event(this, handle, event, cx));
|
||||
true
|
||||
} 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,
|
||||
handle: &Model<E>,
|
||||
mut on_release: impl FnMut(&mut T, &mut E, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: Any + Send,
|
||||
T2: 'static,
|
||||
E: Entity<T2>,
|
||||
{
|
||||
let this = self.weak_handle();
|
||||
self.app.observe_release(handle, move |entity, cx| {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| on_release(this, entity, cx));
|
||||
}
|
||||
})
|
||||
let entity_id = entity.entity_id();
|
||||
let this = self.weak_model();
|
||||
self.app.release_listeners.insert(
|
||||
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>(
|
||||
@ -126,7 +139,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
where
|
||||
T: 'static + Send,
|
||||
{
|
||||
let handle = self.weak_handle();
|
||||
let handle = self.weak_model();
|
||||
self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
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,
|
||||
T: 'static + Send,
|
||||
{
|
||||
let handle = self.weak_handle();
|
||||
let handle = self.weak_model();
|
||||
self.app.quit_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| {
|
||||
@ -187,7 +200,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let this = self.weak_handle();
|
||||
let this = self.weak_model();
|
||||
self.app.spawn(|cx| f(this, cx))
|
||||
}
|
||||
|
||||
@ -199,7 +212,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let this = self.weak_handle();
|
||||
let this = self.weak_model();
|
||||
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)
|
||||
}
|
||||
|
||||
fn update_entity<U: 'static, R>(
|
||||
fn update_model<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<U>,
|
||||
update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> 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)
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
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 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 anyhow::Result;
|
||||
pub use app::*;
|
||||
@ -39,6 +45,7 @@ pub use image_cache::*;
|
||||
pub use interactive::*;
|
||||
pub use keymap::*;
|
||||
pub use platform::*;
|
||||
use private::Sealed;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
pub use serde;
|
||||
@ -80,7 +87,7 @@ pub trait Context {
|
||||
where
|
||||
T: 'static + Send;
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
@ -104,7 +111,7 @@ pub trait VisualContext: Context {
|
||||
) -> Self::Result<R>;
|
||||
}
|
||||
|
||||
pub trait EntityHandle<T> {
|
||||
pub trait Entity<T>: Sealed {
|
||||
type Weak: 'static + Send;
|
||||
|
||||
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,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.0.update_entity(handle, |entity, cx| {
|
||||
self.0.update_model(handle, |entity, cx| {
|
||||
let cx = unsafe {
|
||||
mem::transmute::<
|
||||
&mut C::ModelContext<'_, T>,
|
||||
|
@ -1,14 +1,10 @@
|
||||
use crate::{
|
||||
AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow, Bounds, Component,
|
||||
Element, ElementId, EntityHandle, EntityId, Flatten, LayoutId, Model, Pixels, Size,
|
||||
ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
private::Sealed, AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow,
|
||||
Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels,
|
||||
Size, ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{any::TypeId, marker::PhantomData, sync::Arc};
|
||||
|
||||
pub trait Render: 'static + Sized {
|
||||
type Element: Element<Self> + 'static + Send;
|
||||
@ -20,19 +16,42 @@ pub struct View<V> {
|
||||
pub(crate) model: Model<V>,
|
||||
}
|
||||
|
||||
impl<V> Sealed for View<V> {}
|
||||
|
||||
impl<V: Render> View<V> {
|
||||
pub fn into_any(self) -> AnyView {
|
||||
AnyView(Arc::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> View<V> {
|
||||
pub fn downgrade(&self) -> WeakView<V> {
|
||||
impl<V: 'static> Entity<V> for View<V> {
|
||||
type Weak = WeakView<V>;
|
||||
|
||||
fn entity_id(&self) -> EntityId {
|
||||
self.model.entity_id
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> Self::Weak {
|
||||
WeakView {
|
||||
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>(
|
||||
&self,
|
||||
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(crate) model: WeakModel<V>,
|
||||
}
|
||||
|
||||
impl<V: 'static> WeakView<V> {
|
||||
pub fn upgrade(&self) -> Option<View<V>> {
|
||||
let model = self.model.upgrade()?;
|
||||
Some(View { model })
|
||||
Entity::upgrade_from(self)
|
||||
}
|
||||
|
||||
pub fn update<C, R>(
|
||||
@ -216,7 +215,7 @@ trait ViewObject: Send + Sync {
|
||||
fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
|
||||
fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
|
||||
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>
|
||||
@ -228,7 +227,7 @@ where
|
||||
}
|
||||
|
||||
fn entity_id(&self) -> EntityId {
|
||||
self.model.entity_id
|
||||
Entity::entity_id(self)
|
||||
}
|
||||
|
||||
fn model(&self) -> AnyModel {
|
||||
@ -236,7 +235,7 @@ where
|
||||
}
|
||||
|
||||
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| {
|
||||
let mut any_element = Box::new(AnyElement::new(state.render(cx)));
|
||||
any_element.initialize(state, cx);
|
||||
@ -246,7 +245,7 @@ where
|
||||
}
|
||||
|
||||
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| {
|
||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||
element.layout(state, cx)
|
||||
@ -255,7 +254,7 @@ where
|
||||
}
|
||||
|
||||
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| {
|
||||
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
|
||||
element.paint(state, cx);
|
||||
@ -263,8 +262,10 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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>);
|
||||
|
||||
impl AnyView {
|
||||
pub fn downcast<V: 'static + Send>(self) -> Option<View<V>> {
|
||||
self.0.model().downcast().map(|model| View { model })
|
||||
pub fn downcast<V: 'static>(self) -> Result<View<V>, AnyView> {
|
||||
self.0
|
||||
.model()
|
||||
.downcast()
|
||||
.map(|model| View { model })
|
||||
.map_err(|_| self)
|
||||
}
|
||||
|
||||
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> {
|
||||
view: AnyView,
|
||||
parent_view_state_type: PhantomData<ParentViewState>,
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
||||
Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
|
||||
EntityHandle, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId,
|
||||
GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke,
|
||||
LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
|
||||
PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
|
||||
Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
|
||||
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
|
||||
MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow,
|
||||
Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
|
||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
|
||||
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
|
||||
WindowOptions, SUBPIXEL_VARIANTS,
|
||||
@ -376,23 +376,23 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
self.notify();
|
||||
}
|
||||
|
||||
pub fn subscribe<E, H>(
|
||||
pub fn subscribe<Emitter, E>(
|
||||
&mut self,
|
||||
handle: &H,
|
||||
mut on_event: impl FnMut(H, &E::Event, &mut WindowContext<'_, '_>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_, '_>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
E: EventEmitter,
|
||||
H: EntityHandle<E>,
|
||||
Emitter: EventEmitter,
|
||||
E: Entity<Emitter>,
|
||||
{
|
||||
let entity_id = handle.entity_id();
|
||||
let handle = handle.downgrade();
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.event_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |event, 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");
|
||||
on_event(handle, event, cx);
|
||||
true
|
||||
@ -1280,7 +1280,7 @@ impl Context for WindowContext<'_, '_> {
|
||||
self.entities.insert(slot, model)
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
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));
|
||||
}
|
||||
|
||||
pub fn observe<E>(
|
||||
pub fn observe<V2, E>(
|
||||
&mut self,
|
||||
handle: &Model<E>,
|
||||
mut on_notify: impl FnMut(&mut V, Model<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
E: 'static,
|
||||
V2: 'static,
|
||||
V: Any + Send,
|
||||
E: Entity<V2>,
|
||||
{
|
||||
let view = self.view();
|
||||
let handle = handle.downgrade();
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.observers.insert(
|
||||
handle.entity_id,
|
||||
entity_id,
|
||||
Box::new(move |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))
|
||||
.is_ok()
|
||||
} 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,
|
||||
handle: &H,
|
||||
mut on_event: impl FnMut(&mut V, H, &E::Event, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
E: EventEmitter,
|
||||
H: EntityHandle<E>,
|
||||
V2: EventEmitter,
|
||||
E: Entity<V2>,
|
||||
{
|
||||
let view = self.view();
|
||||
let entity_id = handle.entity_id();
|
||||
let handle = handle.downgrade();
|
||||
let entity_id = entity.entity_id();
|
||||
let handle = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.event_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |event, 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");
|
||||
view.update(cx, |this, cx| on_event(this, handle, event, cx))
|
||||
.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,
|
||||
handle: &Model<T>,
|
||||
mut on_release: impl FnMut(&mut V, &mut T, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
entity: &E,
|
||||
mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, '_, V>) + Send + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
V: Any + Send,
|
||||
V2: 'static,
|
||||
E: Entity<V2>,
|
||||
{
|
||||
let view = self.view();
|
||||
let entity_id = entity.entity_id();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.release_listeners.insert(
|
||||
handle.entity_id,
|
||||
entity_id,
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
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)
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
fn update_model<T: 'static, R>(
|
||||
&mut self,
|
||||
model: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> 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
|
||||
}
|
||||
|
||||
pub fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
||||
self.adapter.enabled_formatters()
|
||||
pub fn prettier_plugins(&self) -> &[&'static str] {
|
||||
self.adapter.prettier_plugins()
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,31 +340,8 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
||||
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(),
|
||||
}
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +379,8 @@ pub struct LanguageConfig {
|
||||
pub overrides: HashMap<String, LanguageConfigOverride>,
|
||||
#[serde(default)]
|
||||
pub word_characters: HashSet<char>,
|
||||
#[serde(default)]
|
||||
pub prettier_parser_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -475,6 +454,7 @@ impl Default for LanguageConfig {
|
||||
overrides: Default::default(),
|
||||
collapsed_placeholder: 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 disk_based_diagnostics_progress_token: Option<String>,
|
||||
pub disk_based_diagnostics_sources: Vec<String>,
|
||||
pub enabled_formatters: Vec<BundledFormatter>,
|
||||
pub prettier_plugins: Vec<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -1604,6 +1584,10 @@ impl Language {
|
||||
override_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prettier_parser_name(&self) -> Option<&str> {
|
||||
self.config.prettier_parser_name.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageScope {
|
||||
@ -1766,7 +1750,7 @@ impl Default for FakeLspAdapter {
|
||||
disk_based_diagnostics_progress_token: None,
|
||||
initialization_options: None,
|
||||
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()
|
||||
}
|
||||
|
||||
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
|
||||
self.enabled_formatters.clone()
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&self.prettier_plugins
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use anyhow::Context;
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashMap;
|
||||
use fs2::Fs;
|
||||
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 node_runtime::NodeRuntime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -189,128 +189,134 @@ impl Prettier {
|
||||
) -> anyhow::Result<Diff> {
|
||||
match self {
|
||||
Self::Real(local) => {
|
||||
let params = buffer.update(cx, |buffer, cx| {
|
||||
let buffer_language = buffer.language();
|
||||
let parsers_with_plugins = buffer_language
|
||||
.into_iter()
|
||||
.flat_map(|language| {
|
||||
language
|
||||
let params = buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
let buffer_language = buffer.language();
|
||||
let parser_with_plugins = buffer_language.and_then(|l| {
|
||||
let prettier_parser = l.prettier_parser_name()?;
|
||||
let mut prettier_plugins = l
|
||||
.lsp_adapters()
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.enabled_formatters())
|
||||
.filter_map(|formatter| match formatter {
|
||||
BundledFormatter::Prettier {
|
||||
parser_name,
|
||||
plugin_names,
|
||||
} => Some((parser_name, plugin_names)),
|
||||
})
|
||||
})
|
||||
.fold(
|
||||
HashMap::default(),
|
||||
|mut parsers_with_plugins, (parser_name, plugins)| {
|
||||
match parser_name {
|
||||
Some(parser_name) => parsers_with_plugins
|
||||
.entry(parser_name)
|
||||
.or_insert_with(HashSet::default)
|
||||
.extend(plugins),
|
||||
None => parsers_with_plugins.values_mut().for_each(|existing_plugins| {
|
||||
existing_plugins.extend(plugins.iter());
|
||||
}),
|
||||
.flat_map(|adapter| adapter.prettier_plugins())
|
||||
.collect::<Vec<_>>();
|
||||
prettier_plugins.dedup();
|
||||
Some((prettier_parser, prettier_plugins))
|
||||
});
|
||||
|
||||
let prettier_node_modules = self.prettier_dir().join("node_modules");
|
||||
anyhow::ensure!(
|
||||
prettier_node_modules.is_dir(),
|
||||
"Prettier node_modules dir does not exist: {prettier_node_modules:?}"
|
||||
);
|
||||
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"),
|
||||
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);
|
||||
}
|
||||
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());
|
||||
if parsers_with_plugins.len() > 1 {
|
||||
log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}");
|
||||
}
|
||||
|
||||
let prettier_node_modules = self.prettier_dir().join("node_modules");
|
||||
anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}");
|
||||
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"),
|
||||
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")?;
|
||||
anyhow::Ok(FormatParams {
|
||||
text: buffer.text(),
|
||||
options: FormatOptions {
|
||||
parser,
|
||||
plugins,
|
||||
path: buffer_path,
|
||||
prettier_options,
|
||||
},
|
||||
})
|
||||
})?
|
||||
.context("prettier params calculation")?;
|
||||
let response = local
|
||||
.server
|
||||
.request::<Format>(params)
|
||||
|
@ -26,8 +26,8 @@ use futures::{
|
||||
};
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use gpui2::{
|
||||
AnyModel, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext,
|
||||
Task, WeakModel,
|
||||
AnyModel, AppContext, AsyncAppContext, Context, Entity, EventEmitter, Executor, Model,
|
||||
ModelContext, Task, WeakModel,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language2::{
|
||||
@ -39,11 +39,11 @@ use language2::{
|
||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||
serialize_anchor, serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter,
|
||||
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
|
||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
|
||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
|
||||
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
|
||||
OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
|
||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp2::{
|
||||
@ -2491,7 +2491,7 @@ impl Project {
|
||||
delay
|
||||
} else {
|
||||
if first_insertion {
|
||||
let this = cx.weak_handle();
|
||||
let this = cx.weak_model();
|
||||
cx.defer(move |cx| {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| {
|
||||
@ -2986,8 +2986,8 @@ impl Project {
|
||||
let this = this.clone();
|
||||
move |mut params, mut cx| {
|
||||
let adapter = adapter.clone();
|
||||
adapter.process_diagnostics(&mut params);
|
||||
if let Some(this) = this.upgrade() {
|
||||
adapter.process_diagnostics(&mut params);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_diagnostics(
|
||||
server_id,
|
||||
@ -8410,12 +8410,7 @@ impl Project {
|
||||
let Some(buffer_language) = buffer.language() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
if !buffer_language
|
||||
.lsp_adapters()
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.enabled_formatters())
|
||||
.any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. }))
|
||||
{
|
||||
if buffer_language.prettier_parser_name().is_none() {
|
||||
return Task::ready(None);
|
||||
}
|
||||
|
||||
@ -8574,16 +8569,15 @@ impl Project {
|
||||
};
|
||||
|
||||
let mut prettier_plugins = None;
|
||||
for formatter in new_language
|
||||
.lsp_adapters()
|
||||
.into_iter()
|
||||
.flat_map(|adapter| adapter.enabled_formatters())
|
||||
{
|
||||
match formatter {
|
||||
BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins
|
||||
.get_or_insert_with(|| HashSet::default())
|
||||
.extend(plugin_names),
|
||||
}
|
||||
if new_language.prettier_parser_name().is_some() {
|
||||
prettier_plugins
|
||||
.get_or_insert_with(|| HashSet::default())
|
||||
.extend(
|
||||
new_language
|
||||
.lsp_adapters()
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.prettier_plugins()),
|
||||
)
|
||||
}
|
||||
let Some(prettier_plugins) = prettier_plugins else {
|
||||
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.
|
||||
if !copilot_server.has_notification_handler::<copilot2::request::LogMessage>() {
|
||||
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
|
||||
.on_notification::<copilot2::request::LogMessage, _>(
|
||||
move |params, mut cx| {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::Project;
|
||||
use gpui2::{AnyWindowHandle, Context, Model, ModelContext, WeakModel};
|
||||
use gpui2::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
|
||||
use settings2::Settings;
|
||||
use std::path::{Path, PathBuf};
|
||||
use terminal2::{
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::BTreeMap;
|
||||
use gpui2::{AppContext, KeyBinding};
|
||||
use gpui2::{AppContext, KeyBinding, SharedString};
|
||||
use schemars::{
|
||||
gen::{SchemaGenerator, SchemaSettings},
|
||||
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
||||
@ -96,7 +96,7 @@ impl KeymapFile {
|
||||
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()
|
||||
.with(|settings| settings.option_add_null_type = false)
|
||||
.into_generator()
|
||||
|
@ -108,6 +108,24 @@ pub struct 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 {
|
||||
self.highlights
|
||||
.iter()
|
||||
|
@ -47,7 +47,7 @@ install_cli = { path = "../install_cli" }
|
||||
journal2 = { path = "../journal2" }
|
||||
language2 = { path = "../language2" }
|
||||
# language_selector = { path = "../language_selector" }
|
||||
lsp = { path = "../lsp" }
|
||||
lsp2 = { path = "../lsp2" }
|
||||
language_tools = { path = "../language_tools" }
|
||||
node_runtime = { path = "../node_runtime" }
|
||||
# assistant = { path = "../assistant" }
|
||||
@ -60,7 +60,7 @@ project2 = { path = "../project2" }
|
||||
# recent_projects = { path = "../recent_projects" }
|
||||
rpc2 = { path = "../rpc2" }
|
||||
settings2 = { path = "../settings2" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
feature_flags2 = { path = "../feature_flags2" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
shellexpand = "2.1.0"
|
||||
text = { path = "../text" }
|
||||
@ -135,6 +135,7 @@ tree-sitter-yaml.workspace = true
|
||||
tree-sitter-lua.workspace = true
|
||||
tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
|
||||
url = "2.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…
Reference in New Issue
Block a user