This commit is contained in:
Antonio Scandurra 2023-10-24 10:01:19 +02:00
parent 4d621f355d
commit 18eb4a7292
10 changed files with 1017 additions and 194 deletions

20
Cargo.lock generated
View File

@ -5815,6 +5815,26 @@ dependencies = [
"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]]
name = "pretty_assertions"
version = "1.4.0"

View File

@ -61,6 +61,7 @@ members = [
"crates/plugin_macros",
"crates/plugin_runtime",
"crates/prettier",
"crates/prettier2",
"crates/project",
"crates/project2",
"crates/project_panel",

View File

@ -22,6 +22,7 @@ use parking_lot::{Mutex, RwLock};
use slotmap::SlotMap;
use std::{
any::{type_name, Any, TypeId},
borrow::Borrow,
mem,
sync::{atomic::Ordering::SeqCst, Arc, Weak},
time::Duration,
@ -670,29 +671,12 @@ impl Context for AppContext {
}
}
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)
})
})
}
impl<C> MainThread<C>
where
C: Borrow<AppContext>,
{
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) {
@ -722,6 +706,28 @@ impl MainThread<AppContext> {
pub fn open_url(&self, url: &str) {
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>(
&mut self,

View File

@ -56,7 +56,7 @@ pub use window::*;
use derive_more::{Deref, DerefMut};
use std::{
any::{Any, TypeId},
borrow::Borrow,
borrow::{Borrow, BorrowMut},
mem,
ops::{Deref, DerefMut},
sync::Arc,
@ -141,20 +141,29 @@ impl<C: Context> Context for MainThread<C> {
}
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
where
F: FnOnce(&mut Self) -> R,
{
self.app_mut().push_text_style(style);
self.borrow_mut().push_text_style(style);
let result = f(self);
self.app_mut().pop_text_style();
self.borrow_mut().pop_text_style();
result
}
fn set_global<T: Send + Sync + 'static>(&mut self, global: T) {
self.app_mut().set_global(global)
self.borrow_mut().set_global(global)
}
}

View File

@ -1,13 +1,13 @@
use crate::{
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent,
MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds,
BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element,
EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread,
MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use collections::HashMap;
@ -17,7 +17,7 @@ use slotmap::SlotMap;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
borrow::Cow,
borrow::{Borrow, BorrowMut, Cow},
fmt::Debug,
future::Future,
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<'_, '_> {
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
type Result<T> = T;
@ -1174,15 +1168,30 @@ impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
}
}
impl BorrowAppContext for WindowContext<'_, '_> {
fn app_mut(&mut self) -> &mut AppContext {
&mut *self.app
impl<'a, 'w> Borrow<AppContext> for WindowContext<'a, 'w> {
fn borrow(&self) -> &AppContext {
&self.app
}
}
pub trait BorrowWindow: BorrowAppContext {
fn window(&self) -> &Window;
fn window_mut(&mut self) -> &mut Window;
impl<'a, 'w> BorrowMut<AppContext> for WindowContext<'a, 'w> {
fn borrow_mut(&mut self) -> &mut AppContext {
&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>(
&mut self,
@ -1205,7 +1214,8 @@ pub trait BorrowWindow: BorrowAppContext {
}
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
}
@ -1308,34 +1318,46 @@ pub trait BorrowWindow: BorrowAppContext {
}
}
impl BorrowWindow for WindowContext<'_, '_> {
fn window(&self) -> &Window {
&*self.window
}
fn window_mut(&mut self) -> &mut Window {
&mut *self.window
impl Borrow<Window> for WindowContext<'_, '_> {
fn borrow(&self) -> &Window {
&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>,
entity_type: PhantomData<S>,
entity_type: PhantomData<V>,
entity_id: EntityId,
}
impl<S> BorrowAppContext for ViewContext<'_, '_, S> {
fn app_mut(&mut self) -> &mut AppContext {
impl<V> Borrow<AppContext> for ViewContext<'_, '_, V> {
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
}
}
impl<S> BorrowWindow for ViewContext<'_, '_, S> {
fn window(&self) -> &Window {
&self.window_cx.window
impl<V> Borrow<Window> for ViewContext<'_, '_, V> {
fn borrow(&self) -> &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
}
}

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

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

View 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}`);
}
}
});
});
}

View File

@ -196,7 +196,7 @@ impl DelayedDebounced {
self.cancel_channel = Some(sender);
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();
if let Some(previous_task) = previous_task {
previous_task.await;
@ -207,7 +207,7 @@ impl DelayedDebounced {
_ = 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;
}
}));
@ -8113,7 +8113,7 @@ impl Project {
if let Some(buffer) = buffer {
buffer.update(cx, |buffer, cx| {
buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
})?;
});
}
Ok(())
})?

View File

@ -1,130 +1,131 @@
use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
use gpui2::color::Color;
use theme2::TerminalStyle;
// todo!()
// use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
// use gpui2::color::Color;
// use theme2::TerminalStyle;
///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
match alac_color {
//Named and theme defined colors
alacritty_terminal::ansi::Color::Named(n) => match n {
alacritty_terminal::ansi::NamedColor::Black => style.black,
alacritty_terminal::ansi::NamedColor::Red => style.red,
alacritty_terminal::ansi::NamedColor::Green => style.green,
alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
alacritty_terminal::ansi::NamedColor::Blue => style.blue,
alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
alacritty_terminal::ansi::NamedColor::White => style.white,
alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
alacritty_terminal::ansi::NamedColor::Background => style.background,
alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
},
//'True' colors
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
//8 bit, indexed colors
alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
}
}
// ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
// pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
// match alac_color {
// //Named and theme defined colors
// alacritty_terminal::ansi::Color::Named(n) => match n {
// alacritty_terminal::ansi::NamedColor::Black => style.black,
// alacritty_terminal::ansi::NamedColor::Red => style.red,
// alacritty_terminal::ansi::NamedColor::Green => style.green,
// alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
// alacritty_terminal::ansi::NamedColor::Blue => style.blue,
// alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
// alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
// alacritty_terminal::ansi::NamedColor::White => style.white,
// alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
// alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
// alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
// alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
// alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
// alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
// alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
// alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
// alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
// alacritty_terminal::ansi::NamedColor::Background => style.background,
// alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
// alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
// alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
// alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
// alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
// alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
// alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
// alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
// alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
// alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
// alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
// },
// //'True' colors
// alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
// //8 bit, indexed colors
// 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.
///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
pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
match index {
//0-15 are the same as the named colors above
0 => style.black,
1 => style.red,
2 => style.green,
3 => style.yellow,
4 => style.blue,
5 => style.magenta,
6 => style.cyan,
7 => style.white,
8 => style.bright_black,
9 => style.bright_red,
10 => style.bright_green,
11 => style.bright_yellow,
12 => style.bright_blue,
13 => style.bright_magenta,
14 => style.bright_cyan,
15 => style.bright_white,
//16-231 are mapped to their RGB colors on a 0-5 range per channel
16..=231 => {
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
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 => {
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
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
256 => style.foreground,
257 => style.background,
258 => style.cursor,
259 => style.dim_black,
260 => style.dim_red,
261 => style.dim_green,
262 => style.dim_yellow,
263 => style.dim_blue,
264 => style.dim_magenta,
265 => style.dim_cyan,
266 => style.dim_white,
267 => style.bright_foreground,
268 => style.black, //'Dim Background', non-standard color
_ => Color::new(0, 0, 0, 255),
}
}
///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).
///
///Wikipedia gives a formula for calculating the index for a given color:
///
///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.
fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
debug_assert!((&16..=&231).contains(&i));
let i = i - 16;
let r = (i - (i % 36)) / 36;
let g = ((i % 36) - (i % 6)) / 6;
let b = (i % 36) % 6;
(r, g, b)
}
// ///Converts an 8 bit ANSI color to it's GPUI equivalent.
// ///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
// pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
// match index {
// //0-15 are the same as the named colors above
// 0 => style.black,
// 1 => style.red,
// 2 => style.green,
// 3 => style.yellow,
// 4 => style.blue,
// 5 => style.magenta,
// 6 => style.cyan,
// 7 => style.white,
// 8 => style.bright_black,
// 9 => style.bright_red,
// 10 => style.bright_green,
// 11 => style.bright_yellow,
// 12 => style.bright_blue,
// 13 => style.bright_magenta,
// 14 => style.bright_cyan,
// 15 => style.bright_white,
// //16-231 are mapped to their RGB colors on a 0-5 range per channel
// 16..=231 => {
// 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
// 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 => {
// 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
// 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
// 256 => style.foreground,
// 257 => style.background,
// 258 => style.cursor,
// 259 => style.dim_black,
// 260 => style.dim_red,
// 261 => style.dim_green,
// 262 => style.dim_yellow,
// 263 => style.dim_blue,
// 264 => style.dim_magenta,
// 265 => style.dim_cyan,
// 266 => style.dim_white,
// 267 => style.bright_foreground,
// 268 => style.black, //'Dim Background', non-standard color
// _ => Color::new(0, 0, 0, 255),
// }
// }
// ///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).
// ///
// ///Wikipedia gives a formula for calculating the index for a given color:
// ///
// ///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.
// fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
// debug_assert!((&16..=&231).contains(&i));
// let i = i - 16;
// let r = (i - (i % 36)) / 36;
// let g = ((i % 36) - (i % 6)) / 6;
// let b = (i % 36) % 6;
// (r, g, b)
// }
//Convenience method to convert from a GPUI color to an alacritty Rgb
pub fn to_alac_rgb(color: Color) -> AlacRgb {
AlacRgb::new(color.r, color.g, color.g)
}
// //Convenience method to convert from a GPUI color to an alacritty Rgb
// pub fn to_alac_rgb(color: Color) -> AlacRgb {
// AlacRgb::new(color.r, color.g, color.g)
// }
#[cfg(test)]
mod tests {
#[test]
fn test_rgb_for_index() {
//Test every possible value in the color cube
for i in 16..=231 {
let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
assert_eq!(i, 16 + 36 * r + 6 * g + b);
}
}
}
// #[cfg(test)]
// mod tests {
// #[test]
// fn test_rgb_for_index() {
// //Test every possible value in the color cube
// for i in 16..=231 {
// let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
// assert_eq!(i, 16 + 36 * r + 6 * g + b);
// }
// }
// }