WIP
This commit is contained in:
parent
4d621f355d
commit
18eb4a7292
10 changed files with 1017 additions and 194 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -5815,6 +5815,26 @@ dependencies = [
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettier2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"client2",
|
||||||
|
"collections",
|
||||||
|
"fs",
|
||||||
|
"futures 0.3.28",
|
||||||
|
"gpui2",
|
||||||
|
"language2",
|
||||||
|
"log",
|
||||||
|
"lsp2",
|
||||||
|
"node_runtime",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
|
@ -61,6 +61,7 @@ members = [
|
||||||
"crates/plugin_macros",
|
"crates/plugin_macros",
|
||||||
"crates/plugin_runtime",
|
"crates/plugin_runtime",
|
||||||
"crates/prettier",
|
"crates/prettier",
|
||||||
|
"crates/prettier2",
|
||||||
"crates/project",
|
"crates/project",
|
||||||
"crates/project2",
|
"crates/project2",
|
||||||
"crates/project_panel",
|
"crates/project_panel",
|
||||||
|
|
|
@ -22,6 +22,7 @@ use parking_lot::{Mutex, RwLock};
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, Any, TypeId},
|
any::{type_name, Any, TypeId},
|
||||||
|
borrow::Borrow,
|
||||||
mem,
|
mem,
|
||||||
sync::{atomic::Ordering::SeqCst, Arc, Weak},
|
sync::{atomic::Ordering::SeqCst, Arc, Weak},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
@ -670,29 +671,12 @@ impl Context for AppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MainThread<AppContext> {
|
impl<C> MainThread<C>
|
||||||
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
where
|
||||||
self.0.update(|cx| {
|
C: Borrow<AppContext>,
|
||||||
update(unsafe {
|
{
|
||||||
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn update_window<R>(
|
|
||||||
&mut self,
|
|
||||||
id: WindowId,
|
|
||||||
update: impl FnOnce(&mut MainThread<WindowContext>) -> R,
|
|
||||||
) -> Result<R> {
|
|
||||||
self.0.update_window(id, |cx| {
|
|
||||||
update(unsafe {
|
|
||||||
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn platform(&self) -> &dyn Platform {
|
pub(crate) fn platform(&self) -> &dyn Platform {
|
||||||
self.platform.borrow_on_main_thread()
|
self.0.borrow().platform.borrow_on_main_thread()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate(&self, ignoring_other_apps: bool) {
|
pub fn activate(&self, ignoring_other_apps: bool) {
|
||||||
|
@ -722,6 +706,28 @@ impl MainThread<AppContext> {
|
||||||
pub fn open_url(&self, url: &str) {
|
pub fn open_url(&self, url: &str) {
|
||||||
self.platform().open_url(url);
|
self.platform().open_url(url);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MainThread<AppContext> {
|
||||||
|
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.0.update(|cx| {
|
||||||
|
update(unsafe {
|
||||||
|
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_window<R>(
|
||||||
|
&mut self,
|
||||||
|
id: WindowId,
|
||||||
|
update: impl FnOnce(&mut MainThread<WindowContext>) -> R,
|
||||||
|
) -> Result<R> {
|
||||||
|
self.0.update_window(id, |cx| {
|
||||||
|
update(unsafe {
|
||||||
|
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_window<S: 'static + Send + Sync>(
|
pub fn open_window<S: 'static + Send + Sync>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub use window::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::Borrow,
|
borrow::{Borrow, BorrowMut},
|
||||||
mem,
|
mem,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -141,20 +141,29 @@ impl<C: Context> Context for MainThread<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BorrowAppContext {
|
pub trait BorrowAppContext {
|
||||||
fn app_mut(&mut self) -> &mut AppContext;
|
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R;
|
||||||
|
|
||||||
|
fn set_global<T: Send + Sync + 'static>(&mut self, global: T);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> BorrowAppContext for C
|
||||||
|
where
|
||||||
|
C: BorrowMut<AppContext>,
|
||||||
|
{
|
||||||
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
self.app_mut().push_text_style(style);
|
self.borrow_mut().push_text_style(style);
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.app_mut().pop_text_style();
|
self.borrow_mut().pop_text_style();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_global<T: Send + Sync + 'static>(&mut self, global: T) {
|
fn set_global<T: Send + Sync + 'static>(&mut self, global: T) {
|
||||||
self.app_mut().set_global(global)
|
self.borrow_mut().set_global(global)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds,
|
||||||
BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
|
BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element,
|
||||||
DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
|
EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
|
||||||
GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
|
InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread,
|
||||||
Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent,
|
MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
|
||||||
MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
|
PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
|
||||||
Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
|
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
|
||||||
SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
|
TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
|
||||||
Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -17,7 +17,7 @@ use slotmap::SlotMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::Cow,
|
borrow::{Borrow, BorrowMut, Cow},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
@ -1122,12 +1122,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'w> MainThread<WindowContext<'a, 'w>> {
|
|
||||||
fn platform(&self) -> &dyn Platform {
|
|
||||||
self.platform.borrow_on_main_thread()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context for WindowContext<'_, '_> {
|
impl Context for WindowContext<'_, '_> {
|
||||||
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
|
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
|
||||||
type Result<T> = T;
|
type Result<T> = T;
|
||||||
|
@ -1174,15 +1168,30 @@ impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BorrowAppContext for WindowContext<'_, '_> {
|
impl<'a, 'w> Borrow<AppContext> for WindowContext<'a, 'w> {
|
||||||
fn app_mut(&mut self) -> &mut AppContext {
|
fn borrow(&self) -> &AppContext {
|
||||||
&mut *self.app
|
&self.app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BorrowWindow: BorrowAppContext {
|
impl<'a, 'w> BorrowMut<AppContext> for WindowContext<'a, 'w> {
|
||||||
fn window(&self) -> &Window;
|
fn borrow_mut(&mut self) -> &mut AppContext {
|
||||||
fn window_mut(&mut self) -> &mut Window;
|
&mut self.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||||
|
fn app_mut(&mut self) -> &mut AppContext {
|
||||||
|
self.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> &Window {
|
||||||
|
self.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_mut(&mut self) -> &mut Window {
|
||||||
|
self.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
fn with_element_id<R>(
|
fn with_element_id<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1205,7 +1214,8 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = f(global_id, self);
|
let result = f(global_id, self);
|
||||||
self.window_mut().element_id_stack.pop();
|
let window: &mut Window = self.borrow_mut();
|
||||||
|
window.element_id_stack.pop();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1308,34 +1318,46 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BorrowWindow for WindowContext<'_, '_> {
|
impl Borrow<Window> for WindowContext<'_, '_> {
|
||||||
fn window(&self) -> &Window {
|
fn borrow(&self) -> &Window {
|
||||||
&*self.window
|
&self.window
|
||||||
}
|
|
||||||
|
|
||||||
fn window_mut(&mut self) -> &mut Window {
|
|
||||||
&mut *self.window
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewContext<'a, 'w, S> {
|
impl BorrowMut<Window> for WindowContext<'_, '_> {
|
||||||
|
fn borrow_mut(&mut self) -> &mut Window {
|
||||||
|
&mut self.window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
|
||||||
|
|
||||||
|
pub struct ViewContext<'a, 'w, V> {
|
||||||
window_cx: WindowContext<'a, 'w>,
|
window_cx: WindowContext<'a, 'w>,
|
||||||
entity_type: PhantomData<S>,
|
entity_type: PhantomData<V>,
|
||||||
entity_id: EntityId,
|
entity_id: EntityId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> BorrowAppContext for ViewContext<'_, '_, S> {
|
impl<V> Borrow<AppContext> for ViewContext<'_, '_, V> {
|
||||||
fn app_mut(&mut self) -> &mut AppContext {
|
fn borrow(&self) -> &AppContext {
|
||||||
|
&*self.window_cx.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> BorrowMut<AppContext> for ViewContext<'_, '_, V> {
|
||||||
|
fn borrow_mut(&mut self) -> &mut AppContext {
|
||||||
&mut *self.window_cx.app
|
&mut *self.window_cx.app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> BorrowWindow for ViewContext<'_, '_, S> {
|
impl<V> Borrow<Window> for ViewContext<'_, '_, V> {
|
||||||
fn window(&self) -> &Window {
|
fn borrow(&self) -> &Window {
|
||||||
&self.window_cx.window
|
&*self.window_cx.window
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn window_mut(&mut self) -> &mut Window {
|
impl<V> BorrowMut<Window> for ViewContext<'_, '_, V> {
|
||||||
|
fn borrow_mut(&mut self) -> &mut Window {
|
||||||
&mut *self.window_cx.window
|
&mut *self.window_cx.window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
crates/prettier2/Cargo.toml
Normal file
34
crates/prettier2/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "prettier2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/prettier2.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
client2 = { path = "../client2" }
|
||||||
|
collections = { path = "../collections"}
|
||||||
|
language2 = { path = "../language2" }
|
||||||
|
gpui2 = { path = "../gpui2" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
|
lsp2 = { path = "../lsp2" }
|
||||||
|
node_runtime = { path = "../node_runtime"}
|
||||||
|
util = { path = "../util" }
|
||||||
|
|
||||||
|
log.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_derive.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
language2 = { path = "../language2", features = ["test-support"] }
|
||||||
|
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
513
crates/prettier2/src/prettier2.rs
Normal file
513
crates/prettier2/src/prettier2.rs
Normal file
|
@ -0,0 +1,513 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use collections::{HashMap, HashSet};
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui2::{AsyncAppContext, Handle};
|
||||||
|
use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
|
||||||
|
use lsp2::{LanguageServer, LanguageServerId};
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::paths::DEFAULT_PRETTIER_DIR;
|
||||||
|
|
||||||
|
pub enum Prettier {
|
||||||
|
Real(RealPrettier),
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Test(TestPrettier),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RealPrettier {
|
||||||
|
worktree_id: Option<usize>,
|
||||||
|
default: bool,
|
||||||
|
prettier_dir: PathBuf,
|
||||||
|
server: Arc<LanguageServer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub struct TestPrettier {
|
||||||
|
worktree_id: Option<usize>,
|
||||||
|
prettier_dir: PathBuf,
|
||||||
|
default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LocateStart {
|
||||||
|
pub worktree_root_path: Arc<Path>,
|
||||||
|
pub starting_path: Arc<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
|
||||||
|
pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
|
||||||
|
const PRETTIER_PACKAGE_NAME: &str = "prettier";
|
||||||
|
const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss";
|
||||||
|
|
||||||
|
impl Prettier {
|
||||||
|
pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[
|
||||||
|
".prettierrc",
|
||||||
|
".prettierrc.json",
|
||||||
|
".prettierrc.json5",
|
||||||
|
".prettierrc.yaml",
|
||||||
|
".prettierrc.yml",
|
||||||
|
".prettierrc.toml",
|
||||||
|
".prettierrc.js",
|
||||||
|
".prettierrc.cjs",
|
||||||
|
"package.json",
|
||||||
|
"prettier.config.js",
|
||||||
|
"prettier.config.cjs",
|
||||||
|
".editorconfig",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier";
|
||||||
|
|
||||||
|
pub async fn locate(
|
||||||
|
starting_path: Option<LocateStart>,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
) -> anyhow::Result<PathBuf> {
|
||||||
|
let paths_to_check = match starting_path.as_ref() {
|
||||||
|
Some(starting_path) => {
|
||||||
|
let worktree_root = starting_path
|
||||||
|
.worktree_root_path
|
||||||
|
.components()
|
||||||
|
.into_iter()
|
||||||
|
.take_while(|path_component| {
|
||||||
|
path_component.as_os_str().to_string_lossy() != "node_modules"
|
||||||
|
})
|
||||||
|
.collect::<PathBuf>();
|
||||||
|
|
||||||
|
if worktree_root != starting_path.worktree_root_path.as_ref() {
|
||||||
|
vec![worktree_root]
|
||||||
|
} else {
|
||||||
|
let (worktree_root_metadata, start_path_metadata) = if starting_path
|
||||||
|
.starting_path
|
||||||
|
.as_ref()
|
||||||
|
== Path::new("")
|
||||||
|
{
|
||||||
|
let worktree_root_data =
|
||||||
|
fs.metadata(&worktree_root).await.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"FS metadata fetch for worktree root path {worktree_root:?}",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
(worktree_root_data.unwrap_or_else(|| {
|
||||||
|
panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
|
||||||
|
}), None)
|
||||||
|
} else {
|
||||||
|
let full_starting_path = worktree_root.join(&starting_path.starting_path);
|
||||||
|
let (worktree_root_data, start_path_data) = futures::try_join!(
|
||||||
|
fs.metadata(&worktree_root),
|
||||||
|
fs.metadata(&full_starting_path),
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("FS metadata fetch for starting path {full_starting_path:?}",)
|
||||||
|
})?;
|
||||||
|
(
|
||||||
|
worktree_root_data.unwrap_or_else(|| {
|
||||||
|
panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
|
||||||
|
}),
|
||||||
|
start_path_data,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match start_path_metadata {
|
||||||
|
Some(start_path_metadata) => {
|
||||||
|
anyhow::ensure!(worktree_root_metadata.is_dir,
|
||||||
|
"For non-empty start path, worktree root {starting_path:?} should be a directory");
|
||||||
|
anyhow::ensure!(
|
||||||
|
!start_path_metadata.is_dir,
|
||||||
|
"For non-empty start path, it should not be a directory {starting_path:?}"
|
||||||
|
);
|
||||||
|
anyhow::ensure!(
|
||||||
|
!start_path_metadata.is_symlink,
|
||||||
|
"For non-empty start path, it should not be a symlink {starting_path:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let file_to_format = starting_path.starting_path.as_ref();
|
||||||
|
let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]);
|
||||||
|
let mut current_path = worktree_root;
|
||||||
|
for path_component in file_to_format.components().into_iter() {
|
||||||
|
current_path = current_path.join(path_component);
|
||||||
|
paths_to_check.push_front(current_path.clone());
|
||||||
|
if path_component.as_os_str().to_string_lossy() == "node_modules" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it
|
||||||
|
Vec::from(paths_to_check)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
anyhow::ensure!(
|
||||||
|
!worktree_root_metadata.is_dir,
|
||||||
|
"For empty start path, worktree root should not be a directory {starting_path:?}"
|
||||||
|
);
|
||||||
|
anyhow::ensure!(
|
||||||
|
!worktree_root_metadata.is_symlink,
|
||||||
|
"For empty start path, worktree root should not be a symlink {starting_path:?}"
|
||||||
|
);
|
||||||
|
worktree_root
|
||||||
|
.parent()
|
||||||
|
.map(|path| vec![path.to_path_buf()])
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match find_closest_prettier_dir(paths_to_check, fs.as_ref())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("finding prettier starting with {starting_path:?}"))?
|
||||||
|
{
|
||||||
|
Some(prettier_dir) => Ok(prettier_dir),
|
||||||
|
None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub async fn start(
|
||||||
|
worktree_id: Option<usize>,
|
||||||
|
_: LanguageServerId,
|
||||||
|
prettier_dir: PathBuf,
|
||||||
|
_: Arc<dyn NodeRuntime>,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
Ok(
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(TestPrettier {
|
||||||
|
worktree_id,
|
||||||
|
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||||
|
prettier_dir,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
|
pub async fn start(
|
||||||
|
worktree_id: Option<usize>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
prettier_dir: PathBuf,
|
||||||
|
node: Arc<dyn NodeRuntime>,
|
||||||
|
cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
use lsp2::LanguageServerBinary;
|
||||||
|
|
||||||
|
let executor = cx.executor();
|
||||||
|
anyhow::ensure!(
|
||||||
|
prettier_dir.is_dir(),
|
||||||
|
"Prettier dir {prettier_dir:?} is not a directory"
|
||||||
|
);
|
||||||
|
let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE);
|
||||||
|
anyhow::ensure!(
|
||||||
|
prettier_server.is_file(),
|
||||||
|
"no prettier server package found at {prettier_server:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let node_path = executor
|
||||||
|
.spawn(async move { node.binary_path().await })
|
||||||
|
.await?;
|
||||||
|
let server = LanguageServer::new(
|
||||||
|
server_id,
|
||||||
|
LanguageServerBinary {
|
||||||
|
path: node_path,
|
||||||
|
arguments: vec![prettier_server.into(), prettier_dir.as_path().into()],
|
||||||
|
},
|
||||||
|
Path::new("/"),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.context("prettier server creation")?;
|
||||||
|
let server = executor
|
||||||
|
.spawn(server.initialize(None))
|
||||||
|
.await
|
||||||
|
.context("prettier server initialization")?;
|
||||||
|
Ok(Self::Real(RealPrettier {
|
||||||
|
worktree_id,
|
||||||
|
server,
|
||||||
|
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||||
|
prettier_dir,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn format(
|
||||||
|
&self,
|
||||||
|
buffer: &Handle<Buffer>,
|
||||||
|
buffer_path: Option<PathBuf>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> 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
|
||||||
|
.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());
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
parsers_with_plugins
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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")?;
|
||||||
|
let response = local
|
||||||
|
.server
|
||||||
|
.request::<Format>(params)
|
||||||
|
.await
|
||||||
|
.context("prettier format request")?;
|
||||||
|
let diff_task = buffer.update(cx, |buffer, cx| buffer.diff(response.text, cx))?;
|
||||||
|
Ok(diff_task.await)
|
||||||
|
}
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(_) => Ok(buffer
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
let formatted_text = buffer.text() + Self::FORMAT_SUFFIX;
|
||||||
|
buffer.diff(formatted_text, cx)
|
||||||
|
})?
|
||||||
|
.await),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear_cache(&self) -> anyhow::Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Real(local) => local
|
||||||
|
.server
|
||||||
|
.request::<ClearCache>(())
|
||||||
|
.await
|
||||||
|
.context("prettier clear cache"),
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn server(&self) -> Option<&Arc<LanguageServer>> {
|
||||||
|
match self {
|
||||||
|
Self::Real(local) => Some(&local.server),
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Real(local) => local.default,
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(test_prettier) => test_prettier.default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prettier_dir(&self) -> &Path {
|
||||||
|
match self {
|
||||||
|
Self::Real(local) => &local.prettier_dir,
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(test_prettier) => &test_prettier.prettier_dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worktree_id(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Self::Real(local) => local.worktree_id,
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
Self::Test(test_prettier) => test_prettier.worktree_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_closest_prettier_dir(
|
||||||
|
paths_to_check: Vec<PathBuf>,
|
||||||
|
fs: &dyn Fs,
|
||||||
|
) -> anyhow::Result<Option<PathBuf>> {
|
||||||
|
for path in paths_to_check {
|
||||||
|
let possible_package_json = path.join("package.json");
|
||||||
|
if let Some(package_json_metadata) = fs
|
||||||
|
.metadata(&possible_package_json)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Fetching metadata for {possible_package_json:?}"))?
|
||||||
|
{
|
||||||
|
if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
|
||||||
|
let package_json_contents = fs
|
||||||
|
.load(&possible_package_json)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("reading {possible_package_json:?} file contents"))?;
|
||||||
|
if let Ok(json_contents) = serde_json::from_str::<HashMap<String, serde_json::Value>>(
|
||||||
|
&package_json_contents,
|
||||||
|
) {
|
||||||
|
if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") {
|
||||||
|
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies")
|
||||||
|
{
|
||||||
|
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
|
||||||
|
if let Some(node_modules_location_metadata) = fs
|
||||||
|
.metadata(&possible_node_modules_location)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
|
||||||
|
{
|
||||||
|
if node_modules_location_metadata.is_dir {
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Format {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FormatParams {
|
||||||
|
text: String,
|
||||||
|
options: FormatOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FormatOptions {
|
||||||
|
plugins: Vec<PathBuf>,
|
||||||
|
parser: Option<String>,
|
||||||
|
#[serde(rename = "filepath")]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
prettier_options: Option<HashMap<String, serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FormatResult {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp2::request::Request for Format {
|
||||||
|
type Params = FormatParams;
|
||||||
|
type Result = FormatResult;
|
||||||
|
const METHOD: &'static str = "prettier/format";
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClearCache {}
|
||||||
|
|
||||||
|
impl lsp2::request::Request for ClearCache {
|
||||||
|
type Params = ();
|
||||||
|
type Result = ();
|
||||||
|
const METHOD: &'static str = "prettier/clear_cache";
|
||||||
|
}
|
217
crates/prettier2/src/prettier_server.js
Normal file
217
crates/prettier2/src/prettier_server.js
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
const { Buffer } = require('buffer');
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { once } = require('events');
|
||||||
|
|
||||||
|
const prettierContainerPath = process.argv[2];
|
||||||
|
if (prettierContainerPath == null || prettierContainerPath.length == 0) {
|
||||||
|
process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
fs.stat(prettierContainerPath, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stats.isDirectory()) {
|
||||||
|
process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier');
|
||||||
|
|
||||||
|
class Prettier {
|
||||||
|
constructor(path, prettier, config) {
|
||||||
|
this.path = path;
|
||||||
|
this.prettier = prettier;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let prettier;
|
||||||
|
let config;
|
||||||
|
try {
|
||||||
|
prettier = await loadPrettier(prettierPath);
|
||||||
|
config = await prettier.resolveConfig(prettierPath) || {};
|
||||||
|
} catch (e) {
|
||||||
|
process.stderr.write(`Failed to load prettier: ${e}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`);
|
||||||
|
process.stdin.resume();
|
||||||
|
handleBuffer(new Prettier(prettierPath, prettier, config));
|
||||||
|
})()
|
||||||
|
|
||||||
|
async function handleBuffer(prettier) {
|
||||||
|
for await (const messageText of readStdin()) {
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(messageText);
|
||||||
|
} catch (e) {
|
||||||
|
sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// allow concurrent request handling by not `await`ing the message handling promise (async function)
|
||||||
|
handleMessage(message, prettier).catch(e => {
|
||||||
|
sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerSeparator = "\r\n";
|
||||||
|
const contentLengthHeaderName = 'Content-Length';
|
||||||
|
|
||||||
|
async function* readStdin() {
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
let streamEnded = false;
|
||||||
|
process.stdin.on('end', () => {
|
||||||
|
streamEnded = true;
|
||||||
|
});
|
||||||
|
process.stdin.on('data', (data) => {
|
||||||
|
buffer = Buffer.concat([buffer, data]);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleStreamEnded(errorMessage) {
|
||||||
|
sendResponse(makeError(errorMessage));
|
||||||
|
buffer = Buffer.alloc(0);
|
||||||
|
messageLength = null;
|
||||||
|
await once(process.stdin, 'readable');
|
||||||
|
streamEnded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let headersLength = null;
|
||||||
|
let messageLength = null;
|
||||||
|
main_loop: while (true) {
|
||||||
|
if (messageLength === null) {
|
||||||
|
while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) {
|
||||||
|
if (streamEnded) {
|
||||||
|
await handleStreamEnded('Unexpected end of stream: headers not found');
|
||||||
|
continue main_loop;
|
||||||
|
} else if (buffer.length > contentLengthHeaderName.length * 10) {
|
||||||
|
await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`);
|
||||||
|
continue main_loop;
|
||||||
|
}
|
||||||
|
await once(process.stdin, 'readable');
|
||||||
|
}
|
||||||
|
const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii');
|
||||||
|
const contentLengthHeader = headers.split(headerSeparator)
|
||||||
|
.map(header => header.split(':'))
|
||||||
|
.filter(header => header[2] === undefined)
|
||||||
|
.filter(header => (header[1] || '').length > 0)
|
||||||
|
.find(header => (header[0] || '').trim() === contentLengthHeaderName);
|
||||||
|
const contentLength = (contentLengthHeader || [])[1];
|
||||||
|
if (contentLength === undefined) {
|
||||||
|
await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`);
|
||||||
|
continue main_loop;
|
||||||
|
}
|
||||||
|
headersLength = headers.length + headerSeparator.length * 2;
|
||||||
|
messageLength = parseInt(contentLength, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (buffer.length < (headersLength + messageLength)) {
|
||||||
|
if (streamEnded) {
|
||||||
|
await handleStreamEnded(
|
||||||
|
`Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`);
|
||||||
|
continue main_loop;
|
||||||
|
}
|
||||||
|
await once(process.stdin, 'readable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageEnd = headersLength + messageLength;
|
||||||
|
const message = buffer.subarray(headersLength, messageEnd);
|
||||||
|
buffer = buffer.subarray(messageEnd);
|
||||||
|
headersLength = null;
|
||||||
|
messageLength = null;
|
||||||
|
yield message.toString('utf8');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
sendResponse(makeError(`Error reading stdin: ${e}`));
|
||||||
|
} finally {
|
||||||
|
process.stdin.off('data', () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMessage(message, prettier) {
|
||||||
|
const { method, id, params } = message;
|
||||||
|
if (method === undefined) {
|
||||||
|
throw new Error(`Message method is undefined: ${JSON.stringify(message)}`);
|
||||||
|
}
|
||||||
|
if (id === undefined) {
|
||||||
|
throw new Error(`Message id is undefined: ${JSON.stringify(message)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'prettier/format') {
|
||||||
|
if (params === undefined || params.text === undefined) {
|
||||||
|
throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`);
|
||||||
|
}
|
||||||
|
if (params.options === undefined) {
|
||||||
|
throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolvedConfig = {};
|
||||||
|
if (params.options.filepath !== undefined) {
|
||||||
|
resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...(params.options.prettierOptions || prettier.config),
|
||||||
|
...resolvedConfig,
|
||||||
|
parser: params.options.parser,
|
||||||
|
plugins: params.options.plugins,
|
||||||
|
path: params.options.filepath
|
||||||
|
};
|
||||||
|
process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`);
|
||||||
|
const formattedText = await prettier.prettier.format(params.text, options);
|
||||||
|
sendResponse({ id, result: { text: formattedText } });
|
||||||
|
} else if (method === 'prettier/clear_cache') {
|
||||||
|
prettier.prettier.clearConfigCache();
|
||||||
|
prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {};
|
||||||
|
sendResponse({ id, result: null });
|
||||||
|
} else if (method === 'initialize') {
|
||||||
|
sendResponse({
|
||||||
|
id,
|
||||||
|
result: {
|
||||||
|
"capabilities": {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown method: ${method}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeError(message) {
|
||||||
|
return {
|
||||||
|
error: {
|
||||||
|
"code": -32600, // invalid request code
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendResponse(response) {
|
||||||
|
const responsePayloadString = JSON.stringify({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
...response
|
||||||
|
});
|
||||||
|
const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`;
|
||||||
|
process.stdout.write(headers + responsePayloadString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPrettier(prettierPath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.access(prettierPath, fs.constants.F_OK, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(`Path '${prettierPath}' does not exist.Error: ${err}`);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
resolve(require(prettierPath));
|
||||||
|
} catch (err) {
|
||||||
|
reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -196,7 +196,7 @@ impl DelayedDebounced {
|
||||||
self.cancel_channel = Some(sender);
|
self.cancel_channel = Some(sender);
|
||||||
|
|
||||||
let previous_task = self.task.take();
|
let previous_task = self.task.take();
|
||||||
self.task = Some(cx.executor().spawn(|workspace, mut cx| async move {
|
self.task = Some(cx.spawn(|project, mut cx| async move {
|
||||||
let mut timer = cx.executor().timer(delay).fuse();
|
let mut timer = cx.executor().timer(delay).fuse();
|
||||||
if let Some(previous_task) = previous_task {
|
if let Some(previous_task) = previous_task {
|
||||||
previous_task.await;
|
previous_task.await;
|
||||||
|
@ -207,7 +207,7 @@ impl DelayedDebounced {
|
||||||
_ = timer => {}
|
_ = timer => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(task) = workspace.update(&mut cx, |workspace, cx| (func)(workspace, cx)) {
|
if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
|
||||||
task.await;
|
task.await;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -8113,7 +8113,7 @@ impl Project {
|
||||||
if let Some(buffer) = buffer {
|
if let Some(buffer) = buffer {
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
|
buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
|
||||||
})?;
|
});
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
|
|
|
@ -1,130 +1,131 @@
|
||||||
use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
|
// todo!()
|
||||||
use gpui2::color::Color;
|
// use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
|
||||||
use theme2::TerminalStyle;
|
// use gpui2::color::Color;
|
||||||
|
// use theme2::TerminalStyle;
|
||||||
|
|
||||||
///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
|
// ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
|
||||||
pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
|
// pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
|
||||||
match alac_color {
|
// match alac_color {
|
||||||
//Named and theme defined colors
|
// //Named and theme defined colors
|
||||||
alacritty_terminal::ansi::Color::Named(n) => match n {
|
// alacritty_terminal::ansi::Color::Named(n) => match n {
|
||||||
alacritty_terminal::ansi::NamedColor::Black => style.black,
|
// alacritty_terminal::ansi::NamedColor::Black => style.black,
|
||||||
alacritty_terminal::ansi::NamedColor::Red => style.red,
|
// alacritty_terminal::ansi::NamedColor::Red => style.red,
|
||||||
alacritty_terminal::ansi::NamedColor::Green => style.green,
|
// alacritty_terminal::ansi::NamedColor::Green => style.green,
|
||||||
alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
|
// alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::Blue => style.blue,
|
// alacritty_terminal::ansi::NamedColor::Blue => style.blue,
|
||||||
alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
|
// alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
|
// alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::White => style.white,
|
// alacritty_terminal::ansi::NamedColor::White => style.white,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
|
// alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
|
// alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
|
// alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
|
// alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
|
// alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
|
// alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
|
// alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
|
// alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
|
||||||
alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
|
// alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
|
||||||
alacritty_terminal::ansi::NamedColor::Background => style.background,
|
// alacritty_terminal::ansi::NamedColor::Background => style.background,
|
||||||
alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
|
// alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
|
||||||
alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
|
// alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
|
||||||
alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
|
// alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
|
||||||
alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
|
// alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
|
||||||
alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
|
// alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
|
// alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
|
||||||
alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
|
// alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
|
// alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
|
// alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
|
// alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
|
||||||
alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
|
// alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
|
||||||
},
|
// },
|
||||||
//'True' colors
|
// //'True' colors
|
||||||
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
|
// alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
|
||||||
//8 bit, indexed colors
|
// //8 bit, indexed colors
|
||||||
alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
|
// alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
///Converts an 8 bit ANSI color to it's GPUI equivalent.
|
// ///Converts an 8 bit ANSI color to it's GPUI equivalent.
|
||||||
///Accepts usize for compatibility with the alacritty::Colors interface,
|
// ///Accepts usize for compatibility with the alacritty::Colors interface,
|
||||||
///Other than that use case, should only be called with values in the [0,255] range
|
// ///Other than that use case, should only be called with values in the [0,255] range
|
||||||
pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
|
// pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
|
||||||
match index {
|
// match index {
|
||||||
//0-15 are the same as the named colors above
|
// //0-15 are the same as the named colors above
|
||||||
0 => style.black,
|
// 0 => style.black,
|
||||||
1 => style.red,
|
// 1 => style.red,
|
||||||
2 => style.green,
|
// 2 => style.green,
|
||||||
3 => style.yellow,
|
// 3 => style.yellow,
|
||||||
4 => style.blue,
|
// 4 => style.blue,
|
||||||
5 => style.magenta,
|
// 5 => style.magenta,
|
||||||
6 => style.cyan,
|
// 6 => style.cyan,
|
||||||
7 => style.white,
|
// 7 => style.white,
|
||||||
8 => style.bright_black,
|
// 8 => style.bright_black,
|
||||||
9 => style.bright_red,
|
// 9 => style.bright_red,
|
||||||
10 => style.bright_green,
|
// 10 => style.bright_green,
|
||||||
11 => style.bright_yellow,
|
// 11 => style.bright_yellow,
|
||||||
12 => style.bright_blue,
|
// 12 => style.bright_blue,
|
||||||
13 => style.bright_magenta,
|
// 13 => style.bright_magenta,
|
||||||
14 => style.bright_cyan,
|
// 14 => style.bright_cyan,
|
||||||
15 => style.bright_white,
|
// 15 => style.bright_white,
|
||||||
//16-231 are mapped to their RGB colors on a 0-5 range per channel
|
// //16-231 are mapped to their RGB colors on a 0-5 range per channel
|
||||||
16..=231 => {
|
// 16..=231 => {
|
||||||
let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
|
// let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
|
||||||
let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
|
// let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
|
||||||
Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
|
// Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
|
||||||
}
|
// }
|
||||||
//232-255 are a 24 step grayscale from black to white
|
// //232-255 are a 24 step grayscale from black to white
|
||||||
232..=255 => {
|
// 232..=255 => {
|
||||||
let i = *index as u8 - 232; //Align index to 0..24
|
// let i = *index as u8 - 232; //Align index to 0..24
|
||||||
let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
|
// let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
|
||||||
Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
|
// Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
|
||||||
}
|
// }
|
||||||
//For compatibility with the alacritty::Colors interface
|
// //For compatibility with the alacritty::Colors interface
|
||||||
256 => style.foreground,
|
// 256 => style.foreground,
|
||||||
257 => style.background,
|
// 257 => style.background,
|
||||||
258 => style.cursor,
|
// 258 => style.cursor,
|
||||||
259 => style.dim_black,
|
// 259 => style.dim_black,
|
||||||
260 => style.dim_red,
|
// 260 => style.dim_red,
|
||||||
261 => style.dim_green,
|
// 261 => style.dim_green,
|
||||||
262 => style.dim_yellow,
|
// 262 => style.dim_yellow,
|
||||||
263 => style.dim_blue,
|
// 263 => style.dim_blue,
|
||||||
264 => style.dim_magenta,
|
// 264 => style.dim_magenta,
|
||||||
265 => style.dim_cyan,
|
// 265 => style.dim_cyan,
|
||||||
266 => style.dim_white,
|
// 266 => style.dim_white,
|
||||||
267 => style.bright_foreground,
|
// 267 => style.bright_foreground,
|
||||||
268 => style.black, //'Dim Background', non-standard color
|
// 268 => style.black, //'Dim Background', non-standard color
|
||||||
_ => Color::new(0, 0, 0, 255),
|
// _ => Color::new(0, 0, 0, 255),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
|
// ///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
|
||||||
///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
|
// ///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
|
||||||
///
|
// ///
|
||||||
///Wikipedia gives a formula for calculating the index for a given color:
|
// ///Wikipedia gives a formula for calculating the index for a given color:
|
||||||
///
|
// ///
|
||||||
///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
|
// ///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
|
||||||
///
|
// ///
|
||||||
///This function does the reverse, calculating the r, g, and b components from a given index.
|
// ///This function does the reverse, calculating the r, g, and b components from a given index.
|
||||||
fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
|
// fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
|
||||||
debug_assert!((&16..=&231).contains(&i));
|
// debug_assert!((&16..=&231).contains(&i));
|
||||||
let i = i - 16;
|
// let i = i - 16;
|
||||||
let r = (i - (i % 36)) / 36;
|
// let r = (i - (i % 36)) / 36;
|
||||||
let g = ((i % 36) - (i % 6)) / 6;
|
// let g = ((i % 36) - (i % 6)) / 6;
|
||||||
let b = (i % 36) % 6;
|
// let b = (i % 36) % 6;
|
||||||
(r, g, b)
|
// (r, g, b)
|
||||||
}
|
// }
|
||||||
|
|
||||||
//Convenience method to convert from a GPUI color to an alacritty Rgb
|
// //Convenience method to convert from a GPUI color to an alacritty Rgb
|
||||||
pub fn to_alac_rgb(color: Color) -> AlacRgb {
|
// pub fn to_alac_rgb(color: Color) -> AlacRgb {
|
||||||
AlacRgb::new(color.r, color.g, color.g)
|
// AlacRgb::new(color.r, color.g, color.g)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[cfg(test)]
|
// #[cfg(test)]
|
||||||
mod tests {
|
// mod tests {
|
||||||
#[test]
|
// #[test]
|
||||||
fn test_rgb_for_index() {
|
// fn test_rgb_for_index() {
|
||||||
//Test every possible value in the color cube
|
// //Test every possible value in the color cube
|
||||||
for i in 16..=231 {
|
// for i in 16..=231 {
|
||||||
let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
|
// let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
|
||||||
assert_eq!(i, 16 + 36 * r + 6 * g + b);
|
// assert_eq!(i, 16 + 36 * r + 6 * g + b);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue