mirror of
https://github.com/tamasfe/taplo.git
synced 2025-08-04 17:08:03 +00:00
feat: restore wasm support and vscode extension
This commit is contained in:
parent
05ead961d8
commit
78856eb767
74 changed files with 8096 additions and 1171 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ Cargo.lock
|
|||
target
|
||||
.vscode/settings.json
|
||||
.cargo/config.toml
|
||||
yarn-error.log
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[workspace]
|
||||
exclude = ["util/test-gen", "util/schema-index"]
|
||||
members = ["crates/*"]
|
||||
exclude = ["util/test-gen", "util/schema-index", "crates/taplo-wasm"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
|
|
@ -11,24 +11,19 @@ categories = ["development-tools", "command-line-utilities"]
|
|||
keywords = ["toml", "linter", "formatter"]
|
||||
|
||||
[features]
|
||||
default = ["lsp"]
|
||||
lsp = ["taplo-lsp"]
|
||||
default = []
|
||||
lsp = ["taplo-lsp", "async-ctrlc"]
|
||||
toml-test = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1", features = ["backtrace"] }
|
||||
async-ctrlc = { version = "1.2.0", features = ["stream"] }
|
||||
atty = "0.2.14"
|
||||
async-ctrlc = { version = "1.2.0", features = ["stream"], optional = true }
|
||||
clap = { version = "3.0.0", features = ["derive", "cargo"] }
|
||||
codespan-reporting = "0.11.1"
|
||||
futures = "0.3"
|
||||
glob = "0.3"
|
||||
hex = "0.4"
|
||||
itertools = "0.10.3"
|
||||
lsp-async-stub = { version = "0.5.0", path = "../lsp-async-stub", features = [
|
||||
"tokio-tcp",
|
||||
"tokio-stdio",
|
||||
] }
|
||||
once_cell = "1.4"
|
||||
regex = "1.4"
|
||||
reqwest = { version = "0.11.4", features = ["json"] }
|
||||
|
@ -44,9 +39,24 @@ tracing = "0.1.29"
|
|||
tracing-subscriber = { version = "0.3.7", features = ["env-filter"] }
|
||||
url = "2.2.2"
|
||||
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs"] }
|
||||
atty = "0.2.14"
|
||||
tokio = { version = "1.19.2", features = [
|
||||
"sync",
|
||||
"fs",
|
||||
"time",
|
||||
"io-std",
|
||||
"rt-multi-thread",
|
||||
"parking_lot",
|
||||
] }
|
||||
lsp-async-stub = { version = "0.5.0", path = "../lsp-async-stub", features = [
|
||||
"tokio-tcp",
|
||||
"tokio-stdio",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { version = "1.19.2", features = ["sync", "parking_lot", "io-util"] }
|
||||
lsp-async-stub = { version = "0.5.0", path = "../lsp-async-stub" }
|
||||
|
||||
[[bin]]
|
||||
name = "taplo"
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
use clap::StructOpt;
|
||||
use std::process::exit;
|
||||
use taplo_cli::{args::TaploArgs, log::setup_stderr_logging, Taplo};
|
||||
use taplo_common::environment::native::NativeEnvironment;
|
||||
use taplo_cli::{
|
||||
args::{Colors, TaploArgs},
|
||||
Taplo,
|
||||
};
|
||||
use taplo_common::{environment::native::NativeEnvironment, log::setup_stderr_logging};
|
||||
use tracing::Instrument;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = TaploArgs::parse();
|
||||
setup_stderr_logging(NativeEnvironment::new(), &cli);
|
||||
setup_stderr_logging(
|
||||
NativeEnvironment::new(),
|
||||
cli.log_spans,
|
||||
cli.verbose,
|
||||
match cli.colors {
|
||||
Colors::Auto => None,
|
||||
Colors::Always => Some(true),
|
||||
Colors::Never => Some(false),
|
||||
},
|
||||
);
|
||||
|
||||
match Taplo::new(NativeEnvironment::new())
|
||||
.execute(cli)
|
||||
|
|
|
@ -16,7 +16,7 @@ impl<E: Environment> Taplo<E> {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn format_stdin(&mut self, cmd: FormatCommand) -> Result<(), anyhow::Error> {
|
||||
let mut source = String::new();
|
||||
self.env.stdin().read_to_string(&mut source).await?;
|
||||
|
@ -63,7 +63,7 @@ impl<E: Environment> Taplo<E> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn format_files(&mut self, cmd: FormatCommand) -> Result<(), anyhow::Error> {
|
||||
if cmd.stdin_filepath.is_some() {
|
||||
tracing::warn!("using `--stdin-filepath` has no effect unless input comes from stdin")
|
||||
|
|
|
@ -60,14 +60,14 @@ impl<E: Environment> Taplo<E> {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn lint_stdin(&self, _cmd: LintCommand) -> Result<(), anyhow::Error> {
|
||||
let mut source = String::new();
|
||||
self.env.stdin().read_to_string(&mut source).await?;
|
||||
self.lint_source("-", &source).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn lint_files(&mut self, cmd: LintCommand) -> Result<(), anyhow::Error> {
|
||||
let config = self.load_config(&cmd.general).await?;
|
||||
|
||||
|
@ -93,7 +93,7 @@ impl<E: Environment> Taplo<E> {
|
|||
}
|
||||
|
||||
async fn lint_file(&self, file: &Path) -> Result<(), anyhow::Error> {
|
||||
let source = self.env.read_file(&file).await?;
|
||||
let source = self.env.read_file(file).await?;
|
||||
let source = String::from_utf8(source)?;
|
||||
self.lint_source(&*file.to_string_lossy(), &source).await
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use args::GeneralArgs;
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use taplo_common::{config::Config, environment::Environment, schema::Schemas};
|
||||
|
||||
pub mod args;
|
||||
pub mod commands;
|
||||
pub mod log;
|
||||
pub mod printing;
|
||||
|
||||
pub struct Taplo<E: Environment> {
|
||||
|
@ -23,11 +20,15 @@ pub struct Taplo<E: Environment> {
|
|||
|
||||
impl<E: Environment> Taplo<E> {
|
||||
pub fn new(env: E) -> Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let http = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let http = reqwest::Client::default();
|
||||
|
||||
Self {
|
||||
schemas: Schemas::new(env.clone(), http),
|
||||
colors: env.atty_stderr(),
|
||||
|
@ -36,7 +37,7 @@ impl<E: Environment> Taplo<E> {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn load_config(&mut self, general: &GeneralArgs) -> Result<Arc<Config>, anyhow::Error> {
|
||||
if let Some(c) = self.config.clone() {
|
||||
return Ok(c);
|
||||
|
@ -53,7 +54,7 @@ impl<E: Environment> Taplo<E> {
|
|||
let mut config = Config::default();
|
||||
if let Some(c) = config_path {
|
||||
tracing::info!(path = ?c, "found configuration file");
|
||||
match self.env.read_file(c).await {
|
||||
match self.env.read_file(&c).await {
|
||||
Ok(cfg) => match toml::from_slice(&cfg) {
|
||||
Ok(c) => config = c,
|
||||
Err(error) => {
|
||||
|
@ -83,7 +84,7 @@ impl<E: Environment> Taplo<E> {
|
|||
Ok(c)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(?cwd))]
|
||||
#[tracing::instrument(skip_all, fields(?cwd))]
|
||||
async fn collect_files(
|
||||
&self,
|
||||
cwd: &Path,
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
use crate::args::{Colors, TaploArgs};
|
||||
use std::io;
|
||||
use taplo_common::environment::Environment;
|
||||
use tracing_subscriber::{
|
||||
fmt::format::FmtSpan, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt,
|
||||
EnvFilter,
|
||||
};
|
||||
|
||||
pub fn setup_stderr_logging(e: impl Environment, taplo: &TaploArgs) {
|
||||
let span_events = if taplo.log_spans {
|
||||
FmtSpan::NEW | FmtSpan::CLOSE
|
||||
} else {
|
||||
FmtSpan::NONE
|
||||
};
|
||||
|
||||
let registry = tracing_subscriber::registry();
|
||||
|
||||
let env_filter = match e.env_var("RUST_LOG") {
|
||||
Some(log) => EnvFilter::new(log),
|
||||
None => EnvFilter::default().add_directive(tracing::Level::INFO.into()),
|
||||
};
|
||||
|
||||
if taplo.verbose {
|
||||
registry
|
||||
.with(env_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(match taplo.colors {
|
||||
Colors::Auto => e.atty_stderr(),
|
||||
Colors::Always => true,
|
||||
Colors::Never => false,
|
||||
})
|
||||
.with_span_events(span_events)
|
||||
.event_format(tracing_subscriber::fmt::format().pretty().with_ansi(
|
||||
match taplo.colors {
|
||||
Colors::Auto => e.atty_stderr(),
|
||||
Colors::Always => true,
|
||||
Colors::Never => false,
|
||||
},
|
||||
))
|
||||
.with_writer(io::stderr),
|
||||
)
|
||||
.init();
|
||||
} else {
|
||||
registry
|
||||
.with(env_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(match taplo.colors {
|
||||
Colors::Auto => e.atty_stderr(),
|
||||
Colors::Always => true,
|
||||
Colors::Never => false,
|
||||
})
|
||||
.event_format(
|
||||
tracing_subscriber::fmt::format()
|
||||
.compact()
|
||||
.with_source_location(false)
|
||||
.with_target(false)
|
||||
.without_time()
|
||||
.with_ansi(match taplo.colors {
|
||||
Colors::Auto => e.atty_stderr(),
|
||||
Colors::Always => true,
|
||||
Colors::Never => false,
|
||||
}),
|
||||
)
|
||||
.without_time()
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_span_events(span_events)
|
||||
.with_writer(io::stderr),
|
||||
)
|
||||
.init();
|
||||
}
|
||||
}
|
|
@ -35,14 +35,20 @@ tap = "1.0.1"
|
|||
taplo = { version = "0.8.0", path = "../taplo", features = ["schema"] }
|
||||
thiserror = "1.0.30"
|
||||
time = { version = "0.3.7", features = ["serde"] }
|
||||
tokio = { version = "1.16.1", features = [
|
||||
tracing = "0.1.29"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1.19.2", features = [
|
||||
"sync",
|
||||
"fs",
|
||||
"time",
|
||||
"io-std",
|
||||
"parking_lot",
|
||||
] }
|
||||
tracing = "0.1.29"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { version = "1.19.2", features = ["sync", "parking_lot", "io-util"] }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -339,10 +339,7 @@ impl Rule {
|
|||
pub fn is_included(&self, path: &Path) -> bool {
|
||||
match &self.file_rule {
|
||||
Some(r) => r.is_match(path),
|
||||
None => {
|
||||
tracing::warn!("no file matches were set up");
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use async_trait::async_trait;
|
||||
use futures::{future::LocalBoxFuture, Future};
|
||||
use futures::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod native;
|
||||
|
||||
/// An environment in which the operations with Taplo are executed.
|
||||
|
@ -18,16 +19,11 @@ pub trait Environment: Clone + Send + Sync + 'static {
|
|||
|
||||
fn now(&self) -> OffsetDateTime;
|
||||
|
||||
fn spawn<F>(&self, fut: F) -> LocalBoxFuture<'static, F::Output>
|
||||
fn spawn<F>(&self, fut: F)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send;
|
||||
|
||||
fn spawn_blocking<F, R>(&self, cb: F) -> LocalBoxFuture<'static, R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static;
|
||||
|
||||
fn spawn_local<F>(&self, fut: F)
|
||||
where
|
||||
F: Future + 'static;
|
||||
|
@ -41,10 +37,7 @@ pub trait Environment: Clone + Send + Sync + 'static {
|
|||
|
||||
fn glob_files(&self, glob: &str) -> Result<Vec<PathBuf>, anyhow::Error>;
|
||||
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: impl AsRef<Path> + 'async_trait,
|
||||
) -> Result<Vec<u8>, anyhow::Error>;
|
||||
async fn read_file(&self, path: &Path) -> Result<Vec<u8>, anyhow::Error>;
|
||||
|
||||
async fn write_file(&self, path: &Path, bytes: &[u8]) -> Result<(), anyhow::Error>;
|
||||
|
||||
|
@ -55,5 +48,5 @@ pub trait Environment: Clone + Send + Sync + 'static {
|
|||
/// Absolute current working dir.
|
||||
fn cwd(&self) -> Option<PathBuf>;
|
||||
|
||||
async fn find_config_file(&self, from: impl AsRef<Path> + 'async_trait) -> Option<PathBuf>;
|
||||
async fn find_config_file(&self, from: &Path) -> Option<PathBuf>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::config::CONFIG_FILE_NAMES;
|
||||
|
||||
use super::Environment;
|
||||
use async_trait::async_trait;
|
||||
use futures::{future::LocalBoxFuture, FutureExt};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -35,22 +36,12 @@ impl Environment for NativeEnvironment {
|
|||
OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
fn spawn<F>(&self, fut: F) -> LocalBoxFuture<'static, F::Output>
|
||||
fn spawn<F>(&self, fut: F)
|
||||
where
|
||||
F: futures::Future + Send + 'static,
|
||||
F::Output: Send,
|
||||
{
|
||||
let handle = self.handle.spawn(fut);
|
||||
{ async move { handle.await.unwrap() } }.boxed_local()
|
||||
}
|
||||
|
||||
fn spawn_blocking<F, R>(&self, cb: F) -> LocalBoxFuture<'static, R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let handle = self.handle.spawn_blocking(cb);
|
||||
async move { handle.await.unwrap() }.boxed_local()
|
||||
self.handle.spawn(fut);
|
||||
}
|
||||
|
||||
fn spawn_local<F>(&self, fut: F)
|
||||
|
@ -91,10 +82,7 @@ impl Environment for NativeEnvironment {
|
|||
Ok(paths.filter_map(Result::ok).collect())
|
||||
}
|
||||
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: impl AsRef<std::path::Path> + 'async_trait,
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
async fn read_file(&self, path: &Path) -> Result<Vec<u8>, anyhow::Error> {
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
}
|
||||
|
||||
|
@ -114,11 +102,8 @@ impl Environment for NativeEnvironment {
|
|||
std::env::current_dir().ok()
|
||||
}
|
||||
|
||||
async fn find_config_file(
|
||||
&self,
|
||||
from: impl AsRef<std::path::Path> + 'async_trait,
|
||||
) -> Option<std::path::PathBuf> {
|
||||
let mut p = from.as_ref();
|
||||
async fn find_config_file(&self, from: &Path) -> Option<std::path::PathBuf> {
|
||||
let mut p = from;
|
||||
|
||||
loop {
|
||||
if let Ok(mut dir) = tokio::fs::read_dir(p).await {
|
||||
|
|
|
@ -16,6 +16,7 @@ pub mod convert;
|
|||
pub mod environment;
|
||||
pub mod schema;
|
||||
pub mod util;
|
||||
pub mod log;
|
||||
|
||||
pub type HashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
|
||||
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, ahash::RandomState>;
|
||||
|
|
118
crates/taplo-common/src/log.rs
Normal file
118
crates/taplo-common/src/log.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
use std::io::{self, Write};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, prelude::*, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
use crate::environment::Environment;
|
||||
|
||||
struct BlockingWrite<W: AsyncWrite>(W);
|
||||
|
||||
impl<W: AsyncWrite + Unpin> Write for BlockingWrite<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
futures::executor::block_on(self.0.write(buf))
|
||||
}
|
||||
|
||||
// On WASM we cannot do blocking writes without blocking
|
||||
// the event loop, so we simply do not wait.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use futures::FutureExt;
|
||||
|
||||
let _ = self.0.write_all(buf).boxed_local().poll_unpin(
|
||||
&mut futures::task::Context::from_waker(&futures::task::noop_waker()),
|
||||
);
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
futures::executor::block_on(self.0.flush())
|
||||
}
|
||||
|
||||
// On WASM we cannot do blocking writes without blocking
|
||||
// the event loop, so we simply do not wait.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use futures::FutureExt;
|
||||
|
||||
let _ =
|
||||
self.0
|
||||
.flush()
|
||||
.boxed_local()
|
||||
.poll_unpin(&mut futures::task::Context::from_waker(
|
||||
&futures::task::noop_waker(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_stderr_logging(e: impl Environment, spans: bool, verbose: bool, colors: Option<bool>) {
|
||||
let span_events = if spans {
|
||||
FmtSpan::NEW | FmtSpan::CLOSE
|
||||
} else {
|
||||
FmtSpan::NONE
|
||||
};
|
||||
|
||||
let registry = tracing_subscriber::registry();
|
||||
|
||||
let env_filter = match e.env_var("RUST_LOG") {
|
||||
Some(log) => EnvFilter::new(log),
|
||||
None => EnvFilter::default().add_directive(tracing::Level::INFO.into()),
|
||||
};
|
||||
|
||||
if verbose {
|
||||
registry
|
||||
.with(env_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(match colors {
|
||||
None => e.atty_stderr(),
|
||||
Some(v) => v,
|
||||
})
|
||||
.with_span_events(span_events)
|
||||
.event_format(tracing_subscriber::fmt::format().pretty().with_ansi(
|
||||
match colors {
|
||||
None => e.atty_stderr(),
|
||||
Some(v) => v,
|
||||
},
|
||||
))
|
||||
.with_writer(move || BlockingWrite(e.stderr())),
|
||||
)
|
||||
.try_init()
|
||||
.ok();
|
||||
} else {
|
||||
registry
|
||||
.with(env_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(match colors {
|
||||
None => e.atty_stderr(),
|
||||
Some(v) => v,
|
||||
})
|
||||
.event_format(
|
||||
tracing_subscriber::fmt::format()
|
||||
.compact()
|
||||
.with_source_location(false)
|
||||
.with_target(false)
|
||||
.without_time()
|
||||
.with_ansi(match colors {
|
||||
None => e.atty_stderr(),
|
||||
Some(v) => v,
|
||||
}),
|
||||
)
|
||||
.without_time()
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_span_events(span_events)
|
||||
.with_writer(move || BlockingWrite(e.stderr())),
|
||||
)
|
||||
.try_init()
|
||||
.ok();
|
||||
}
|
||||
}
|
|
@ -328,7 +328,8 @@ impl<E: Environment> SchemaAssociations<E> {
|
|||
.read_file(
|
||||
self.env
|
||||
.to_file_path(index_url)
|
||||
.ok_or_else(|| anyhow!("invalid file path"))?,
|
||||
.ok_or_else(|| anyhow!("invalid file path"))?
|
||||
.as_ref(),
|
||||
)
|
||||
.await?,
|
||||
)?),
|
||||
|
|
|
@ -59,7 +59,7 @@ impl<E: Environment> Schemas<E> {
|
|||
}
|
||||
|
||||
impl<E: Environment> Schemas<E> {
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%schema_url))]
|
||||
#[tracing::instrument(skip_all, fields(%schema_url))]
|
||||
pub async fn validate_root(
|
||||
&self,
|
||||
schema_url: &Url,
|
||||
|
@ -73,7 +73,7 @@ impl<E: Environment> Schemas<E> {
|
|||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%schema_url))]
|
||||
#[tracing::instrument(skip_all, fields(%schema_url))]
|
||||
pub async fn validate(
|
||||
&self,
|
||||
schema_url: &Url,
|
||||
|
@ -161,7 +161,7 @@ impl<E: Environment> Schemas<E> {
|
|||
drop(self.cache.store(schema_url.clone(), schema).await);
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%schema_url))]
|
||||
#[tracing::instrument(skip_all, fields(%schema_url))]
|
||||
pub async fn load_schema(&self, schema_url: &Url) -> Result<Arc<Value>, anyhow::Error> {
|
||||
if let Ok(s) = self.cache.load(schema_url, false).await {
|
||||
tracing::debug!(%schema_url, "schema was found in cache");
|
||||
|
@ -255,7 +255,8 @@ impl<E: Environment> Schemas<E> {
|
|||
.read_file(
|
||||
self.env
|
||||
.to_file_path(schema_url)
|
||||
.ok_or_else(|| anyhow!("invalid file path"))?,
|
||||
.ok_or_else(|| anyhow!("invalid file path"))?
|
||||
.as_ref(),
|
||||
)
|
||||
.await?,
|
||||
)?),
|
||||
|
@ -265,7 +266,7 @@ impl<E: Environment> Schemas<E> {
|
|||
}
|
||||
|
||||
impl<E: Environment> Schemas<E> {
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%schema_url, %path))]
|
||||
#[tracing::instrument(skip_all, fields(%schema_url, %path))]
|
||||
pub async fn schemas_at_path(
|
||||
&self,
|
||||
schema_url: &Url,
|
||||
|
@ -292,7 +293,7 @@ impl<E: Environment> Schemas<E> {
|
|||
Ok(schemas)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%path))]
|
||||
#[tracing::instrument(skip_all, fields(%path))]
|
||||
#[async_recursion(?Send)]
|
||||
#[must_use]
|
||||
async fn collect_schemas(
|
||||
|
@ -431,7 +432,7 @@ impl<E: Environment> Schemas<E> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%schema_url, %path))]
|
||||
#[tracing::instrument(skip_all, fields(%schema_url, %path))]
|
||||
pub async fn possible_schemas_from(
|
||||
&self,
|
||||
schema_url: &Url,
|
||||
|
|
|
@ -37,4 +37,3 @@ taplo-common = { version = "0.1.2", path = "../taplo-common" }
|
|||
time = { version = "0.3", features = ["formatting", "parsing"] }
|
||||
toml = "0.5"
|
||||
tracing = "0.1.29"
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use lsp_types::{
|
|||
use taplo::dom::{KeyOrIndex, Node};
|
||||
use taplo_common::environment::Environment;
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn publish_diagnostics<E: Environment>(
|
||||
mut context: Context<World<E>>,
|
||||
ws_url: Url,
|
||||
|
@ -102,7 +102,7 @@ pub(crate) async fn publish_diagnostics<E: Environment>(
|
|||
.unwrap_or_else(|err| tracing::error!("{err}"));
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn clear_diagnostics<E: Environment>(
|
||||
mut context: Context<World<E>>,
|
||||
document_url: Url,
|
||||
|
@ -117,7 +117,7 @@ pub(crate) async fn clear_diagnostics<E: Environment>(
|
|||
.unwrap_or_else(|err| tracing::error!("{}", err));
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn collect_syntax_errors(doc: &DocumentState, diags: &mut Vec<Diagnostic>) {
|
||||
diags.extend(doc.parse.errors.iter().map(|e| {
|
||||
let range = doc.mapper.range(e.range).unwrap_or_default().into_lsp();
|
||||
|
@ -135,7 +135,7 @@ fn collect_syntax_errors(doc: &DocumentState, diags: &mut Vec<Diagnostic>) {
|
|||
}));
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn collect_dom_errors(
|
||||
doc: &DocumentState,
|
||||
dom: &Node,
|
||||
|
@ -290,7 +290,7 @@ fn collect_dom_errors(
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(%document_url))]
|
||||
#[tracing::instrument(skip_all, fields(%document_url))]
|
||||
async fn collect_schema_errors<E: Environment>(
|
||||
ws: &WorkspaceState<E>,
|
||||
doc: &DocumentState,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use lsp_async_stub::{util::Mapper, Context, Params};
|
||||
use lsp_types::{
|
||||
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
|
||||
DidSaveTextDocumentParams,
|
||||
};
|
||||
use taplo_common::{
|
||||
environment::Environment,
|
||||
|
@ -12,7 +13,7 @@ use crate::{
|
|||
world::{DocumentState, World},
|
||||
};
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn document_open<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<DidOpenTextDocumentParams>,
|
||||
|
@ -57,7 +58,7 @@ pub(crate) async fn document_open<E: Environment>(
|
|||
diagnostics::publish_diagnostics(context.clone(), ws_root, p.text_document.uri).await;
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn document_change<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<DidChangeTextDocumentParams>,
|
||||
|
@ -108,7 +109,15 @@ pub(crate) async fn document_change<E: Environment>(
|
|||
diagnostics::publish_diagnostics(context.clone(), ws_root, p.text_document.uri).await;
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn document_save<E: Environment>(
|
||||
_context: Context<World<E>>,
|
||||
_params: Params<DidSaveTextDocumentParams>,
|
||||
) {
|
||||
// stub to silence warnings
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn document_close<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<DidCloseTextDocumentParams>,
|
||||
|
|
|
@ -18,7 +18,7 @@ use taplo_common::environment::Environment;
|
|||
|
||||
use crate::world::World;
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn folding_ranges<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<FoldingRangeParams>,
|
||||
|
@ -35,7 +35,7 @@ pub(crate) async fn folding_ranges<E: Environment>(
|
|||
)))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn create_folding_ranges(syntax: &SyntaxNode, mapper: &Mapper) -> Vec<FoldingRange> {
|
||||
let mut folding_ranges = Vec::with_capacity(20);
|
||||
|
||||
|
|
|
@ -7,15 +7,15 @@ use crate::World;
|
|||
use lsp_async_stub::{rpc::Error, Context, Params};
|
||||
use lsp_types::{
|
||||
CompletionOptions, DocumentLinkOptions, FoldingRangeProviderCapability,
|
||||
HoverProviderCapability, OneOf, RenameOptions, SemanticTokensFullOptions, SemanticTokensLegend,
|
||||
SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
|
||||
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
|
||||
HoverProviderCapability, InitializedParams, OneOf, RenameOptions, SemanticTokensFullOptions,
|
||||
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities,
|
||||
ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind,
|
||||
WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
|
||||
};
|
||||
use lsp_types::{InitializeParams, InitializeResult};
|
||||
use taplo_common::environment::Environment;
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn initialize<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<InitializeParams>,
|
||||
|
@ -77,11 +77,6 @@ pub async fn initialize<E: Environment>(
|
|||
range: Some(false),
|
||||
}),
|
||||
),
|
||||
// code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
|
||||
// code_action_kinds: Some(vec![CodeActionKind::REFACTOR]),
|
||||
// resolve_provider: None,
|
||||
// work_done_progress_options: Default::default(),
|
||||
// })),
|
||||
rename_provider: Some(OneOf::Right(RenameOptions {
|
||||
prepare_provider: Some(true),
|
||||
work_done_progress_options: Default::default(),
|
||||
|
@ -115,3 +110,11 @@ pub async fn initialize<E: Environment>(
|
|||
offset_encoding: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn initialized<E: Environment>(
|
||||
_context: Context<World<E>>,
|
||||
_params: Params<InitializedParams>,
|
||||
) {
|
||||
// stub to silence warnings.
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use taplo::{
|
|||
};
|
||||
use taplo_common::environment::Environment;
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn semantic_tokens<E: Environment>(
|
||||
context: Context<World<E>>,
|
||||
params: Params<SemanticTokensParams>,
|
||||
|
@ -66,7 +66,7 @@ impl TokenModifier {
|
|||
pub const MODIFIERS: &'static [SemanticTokenModifier] = &[SemanticTokenModifier::READONLY];
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn create_tokens(syntax: &SyntaxNode, mapper: &Mapper) -> Vec<SemanticToken> {
|
||||
let mut builder = SemanticTokensBuilder::new(mapper);
|
||||
|
||||
|
|
|
@ -38,8 +38,10 @@ pub fn create_server<E: Environment>() -> Server<World<E>> {
|
|||
.on_request::<request::SemanticTokensFullRequest, _>(handlers::semantic_tokens)
|
||||
.on_request::<request::PrepareRenameRequest, _>(handlers::prepare_rename)
|
||||
.on_request::<request::Rename, _>(handlers::rename)
|
||||
.on_notification::<notification::Initialized, _>(handlers::initialized)
|
||||
.on_notification::<notification::DidOpenTextDocument, _>(handlers::document_open)
|
||||
.on_notification::<notification::DidChangeTextDocument, _>(handlers::document_change)
|
||||
.on_notification::<notification::DidSaveTextDocument, _>(handlers::document_save)
|
||||
.on_notification::<notification::DidCloseTextDocument, _>(handlers::document_close)
|
||||
.on_notification::<notification::DidChangeConfiguration, _>(handlers::configuration_change)
|
||||
.on_notification::<notification::DidChangeWorkspaceFolders, _>(handlers::workspace_change)
|
||||
|
|
|
@ -116,17 +116,25 @@ pub struct WorkspaceState<E: Environment> {
|
|||
|
||||
impl<E: Environment> WorkspaceState<E> {
|
||||
pub(crate) fn new(env: E, root: Url) -> Self {
|
||||
let client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
client = reqwest::Client::builder().build().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Self {
|
||||
root,
|
||||
documents: Default::default(),
|
||||
taplo_config: Default::default(),
|
||||
schemas: Schemas::new(
|
||||
env,
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
schemas: Schemas::new(env, client),
|
||||
config: LspConfig::default(),
|
||||
}
|
||||
}
|
||||
|
|
41
crates/taplo-wasm/Cargo.toml
Normal file
41
crates/taplo-wasm/Cargo.toml
Normal file
|
@ -0,0 +1,41 @@
|
|||
[package]
|
||||
name = "taplo-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
async-trait = "0.1.56"
|
||||
clap = { version = "3.1.18", features = ["derive"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.21"
|
||||
js-sys = "0.3.57"
|
||||
lsp-async-stub = { path = "../lsp-async-stub" }
|
||||
serde = { version = "1.0.137", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.4.3"
|
||||
serde_json = "1.0.81"
|
||||
taplo = { path = "../taplo" }
|
||||
taplo-cli = { path = "../taplo-cli", optional = true }
|
||||
taplo-common = { path = "../taplo-common" }
|
||||
taplo-lsp = { path = "../taplo-lsp", optional = true }
|
||||
time = { version = "0.3.9", features = ["parsing"] }
|
||||
tokio = "1.19.2"
|
||||
tracing = "0.1.35"
|
||||
url = "2.2.2"
|
||||
wasm-bindgen = { version = "0.2.80" }
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cli = ["taplo-cli"]
|
||||
lsp = ["taplo-lsp"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 's'
|
||||
lto = true
|
366
crates/taplo-wasm/src/environment.rs
Normal file
366
crates/taplo-wasm/src/environment.rs
Normal file
|
@ -0,0 +1,366 @@
|
|||
use anyhow::anyhow;
|
||||
use futures::FutureExt;
|
||||
use js_sys::{Function, Promise, Uint8Array};
|
||||
use std::{
|
||||
io,
|
||||
path::Path,
|
||||
pin::Pin,
|
||||
task::{self, Poll},
|
||||
};
|
||||
use taplo_common::environment::Environment;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use url::Url;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||
|
||||
pub(crate) struct JsAsyncRead {
|
||||
fut: Option<JsFuture>,
|
||||
f: Function,
|
||||
}
|
||||
|
||||
impl JsAsyncRead {
|
||||
fn new(cb: Function) -> Self {
|
||||
Self { fut: None, f: cb }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for JsAsyncRead {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> task::Poll<std::io::Result<()>> {
|
||||
if self.fut.is_none() {
|
||||
let this = JsValue::null();
|
||||
let ret: JsValue = match self.f.call1(&this, &JsValue::from(buf.remaining())) {
|
||||
Ok(val) => val,
|
||||
Err(error) => {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{:?}", error),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let promise = match Promise::try_from(ret) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{:?}", err),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
self.fut = Some(JsFuture::from(promise));
|
||||
}
|
||||
|
||||
if let Some(fut) = self.fut.as_mut() {
|
||||
match fut.poll_unpin(cx) {
|
||||
task::Poll::Ready(val) => {
|
||||
let res = match val {
|
||||
Ok(chunk) => {
|
||||
let arr = js_sys::Uint8Array::from(chunk).to_vec();
|
||||
if !arr.is_empty() {
|
||||
buf.put_slice(&arr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))),
|
||||
};
|
||||
|
||||
self.fut = None;
|
||||
|
||||
Poll::Ready(res)
|
||||
}
|
||||
task::Poll::Pending => Poll::Pending,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsAsyncWrite {
|
||||
fn new(cb: Function) -> Self {
|
||||
Self { fut: None, f: cb }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct JsAsyncWrite {
|
||||
fut: Option<JsFuture>,
|
||||
f: Function,
|
||||
}
|
||||
|
||||
impl AsyncWrite for JsAsyncWrite {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> task::Poll<Result<usize, std::io::Error>> {
|
||||
if self.fut.is_none() {
|
||||
let this = JsValue::null();
|
||||
|
||||
let ret: JsValue = match self.f.call1(&this, &Uint8Array::from(buf).into()) {
|
||||
Ok(val) => val,
|
||||
Err(error) => {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{:?}", error),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let promise = match Promise::try_from(ret) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{:?}", err),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
self.fut = Some(JsFuture::from(promise));
|
||||
}
|
||||
|
||||
if let Some(fut) = self.fut.as_mut() {
|
||||
match fut.poll_unpin(cx) {
|
||||
task::Poll::Ready(val) => {
|
||||
let res = match val {
|
||||
Ok(num_written) => {
|
||||
let n = num_written.as_f64().unwrap_or(0.0).floor() as usize;
|
||||
Ok(n)
|
||||
}
|
||||
Err(err) => Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))),
|
||||
};
|
||||
|
||||
self.fut = None;
|
||||
|
||||
Poll::Ready(res)
|
||||
}
|
||||
task::Poll::Pending => Poll::Pending,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut task::Context<'_>,
|
||||
) -> task::Poll<Result<(), std::io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut task::Context<'_>,
|
||||
) -> task::Poll<Result<(), std::io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WasmEnvironment {
|
||||
js_now: Function,
|
||||
js_env_var: Function,
|
||||
js_atty_stderr: Function,
|
||||
js_on_stdin: Function,
|
||||
js_on_stdout: Function,
|
||||
js_on_stderr: Function,
|
||||
js_glob_files: Function,
|
||||
js_read_file: Function,
|
||||
js_write_file: Function,
|
||||
js_to_file_path: Function,
|
||||
js_is_absolute: Function,
|
||||
js_cwd: Function,
|
||||
js_find_config_file: Function,
|
||||
}
|
||||
|
||||
impl From<JsValue> for WasmEnvironment {
|
||||
fn from(val: JsValue) -> Self {
|
||||
Self {
|
||||
js_now: js_sys::Reflect::get(&val, &JsValue::from_str("js_now"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_env_var: js_sys::Reflect::get(&val, &JsValue::from_str("js_env_var"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_atty_stderr: js_sys::Reflect::get(&val, &JsValue::from_str("js_atty_stderr"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_on_stdin: js_sys::Reflect::get(&val, &JsValue::from_str("js_on_stdin"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_on_stdout: js_sys::Reflect::get(&val, &JsValue::from_str("js_on_stdout"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_on_stderr: js_sys::Reflect::get(&val, &JsValue::from_str("js_on_stderr"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_glob_files: js_sys::Reflect::get(&val, &JsValue::from_str("js_glob_files"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_read_file: js_sys::Reflect::get(&val, &JsValue::from_str("js_read_file"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_write_file: js_sys::Reflect::get(&val, &JsValue::from_str("js_write_file"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_to_file_path: js_sys::Reflect::get(&val, &JsValue::from_str("js_to_file_path"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_is_absolute: js_sys::Reflect::get(&val, &JsValue::from_str("js_is_absolute"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_cwd: js_sys::Reflect::get(&val, &JsValue::from_str("js_cwd"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
js_find_config_file: js_sys::Reflect::get(
|
||||
&val,
|
||||
&JsValue::from_str("js_find_config_file"),
|
||||
)
|
||||
.unwrap()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: we're in a single-threaded WASM environment.
|
||||
unsafe impl Send for WasmEnvironment {}
|
||||
unsafe impl Sync for WasmEnvironment {}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Environment for WasmEnvironment {
|
||||
type Stdin = JsAsyncRead;
|
||||
type Stdout = JsAsyncWrite;
|
||||
type Stderr = JsAsyncWrite;
|
||||
|
||||
fn now(&self) -> OffsetDateTime {
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_now.call0(&this).unwrap();
|
||||
let s: String = js_sys::Date::from(res).to_iso_string().into();
|
||||
OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339).unwrap()
|
||||
}
|
||||
|
||||
fn spawn<F>(&self, fut: F)
|
||||
where
|
||||
F: std::future::Future + Send + 'static,
|
||||
F::Output: Send,
|
||||
{
|
||||
spawn_local(async move {
|
||||
fut.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_local<F>(&self, fut: F)
|
||||
where
|
||||
F: std::future::Future + 'static,
|
||||
{
|
||||
spawn_local(async move {
|
||||
fut.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn env_var(&self, name: &str) -> Option<String> {
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self
|
||||
.js_env_var
|
||||
.call1(&this, &JsValue::from_str(name))
|
||||
.unwrap();
|
||||
res.as_string()
|
||||
}
|
||||
|
||||
fn atty_stderr(&self) -> bool {
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_atty_stderr.call0(&this).unwrap();
|
||||
res.as_bool().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn stdin(&self) -> Self::Stdin {
|
||||
JsAsyncRead::new(self.js_on_stdin.clone())
|
||||
}
|
||||
|
||||
fn stdout(&self) -> Self::Stdout {
|
||||
JsAsyncWrite::new(self.js_on_stdout.clone())
|
||||
}
|
||||
|
||||
fn stderr(&self) -> Self::Stderr {
|
||||
JsAsyncWrite::new(self.js_on_stderr.clone())
|
||||
}
|
||||
|
||||
fn glob_files(&self, glob: &str) -> Result<Vec<std::path::PathBuf>, anyhow::Error> {
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self
|
||||
.js_glob_files
|
||||
.call1(&this, &JsValue::from_str(glob))
|
||||
.unwrap();
|
||||
serde_wasm_bindgen::from_value(res).map_err(|err| anyhow!("{err}"))
|
||||
}
|
||||
|
||||
async fn read_file(&self, path: &Path) -> Result<Vec<u8>, anyhow::Error> {
|
||||
let path_str = JsValue::from_str(&path.to_string_lossy());
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_read_file.call1(&this, &path_str).unwrap();
|
||||
|
||||
let ret = JsFuture::from(Promise::from(res))
|
||||
.await
|
||||
.map_err(|err| anyhow!("{:?}", err))?;
|
||||
|
||||
Ok(Uint8Array::from(ret).to_vec())
|
||||
}
|
||||
|
||||
async fn write_file(&self, path: &Path, bytes: &[u8]) -> Result<(), anyhow::Error> {
|
||||
let path_str = JsValue::from_str(&path.to_string_lossy());
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self
|
||||
.js_write_file
|
||||
.call2(&this, &path_str, &JsValue::from(Uint8Array::from(bytes)))
|
||||
.unwrap();
|
||||
|
||||
Ok(serde_wasm_bindgen::from_value(
|
||||
JsFuture::from(Promise::from(res))
|
||||
.await
|
||||
.map_err(|err| anyhow!("{:?}", err))?,
|
||||
)
|
||||
.map_err(|err| anyhow!("{err}"))?)
|
||||
}
|
||||
|
||||
fn to_file_path(&self, url: &Url) -> Option<std::path::PathBuf> {
|
||||
let url_str = JsValue::from_str(url.as_str());
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_to_file_path.call1(&this, &url_str).unwrap();
|
||||
|
||||
res.as_string().map(Into::into)
|
||||
}
|
||||
|
||||
fn is_absolute(&self, path: &Path) -> bool {
|
||||
let path_str = JsValue::from_str(&path.to_string_lossy());
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_is_absolute.call1(&this, &path_str).unwrap();
|
||||
|
||||
res.is_truthy()
|
||||
}
|
||||
|
||||
fn cwd(&self) -> Option<std::path::PathBuf> {
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_cwd.call0(&this).unwrap();
|
||||
|
||||
res.as_string().map(Into::into)
|
||||
}
|
||||
|
||||
async fn find_config_file(&self, from: &Path) -> Option<std::path::PathBuf> {
|
||||
let path_str = JsValue::from_str(&from.to_string_lossy());
|
||||
let this = JsValue::null();
|
||||
let res: JsValue = self.js_find_config_file.call1(&this, &path_str).unwrap();
|
||||
|
||||
if res.is_undefined() {
|
||||
return None;
|
||||
}
|
||||
|
||||
res.as_string().map(Into::into)
|
||||
}
|
||||
}
|
226
crates/taplo-wasm/src/lib.rs
Normal file
226
crates/taplo-wasm/src/lib.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
use environment::WasmEnvironment;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use taplo::{dom::Node, formatter, parser::parse};
|
||||
use taplo_common::{config::Config, schema::Schemas};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod environment;
|
||||
#[cfg(feature = "lsp")]
|
||||
mod lsp;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Range {
|
||||
start: u32,
|
||||
end: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LintError {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
range: Option<Range>,
|
||||
error: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LintResult {
|
||||
errors: Vec<LintError>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn initialize() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn format(
|
||||
env: JsValue,
|
||||
toml: &str,
|
||||
options: JsValue,
|
||||
config: JsValue,
|
||||
) -> Result<String, JsError> {
|
||||
let mut config = if config.is_undefined() {
|
||||
Config::default()
|
||||
} else {
|
||||
serde_wasm_bindgen::from_value(config)?
|
||||
};
|
||||
|
||||
let env = WasmEnvironment::from(env);
|
||||
config
|
||||
.prepare(&env, Path::new("/"))
|
||||
.map_err(|err| JsError::new(&err.to_string()))?;
|
||||
|
||||
let camel_opts: formatter::OptionsIncompleteCamel = serde_wasm_bindgen::from_value(options)?;
|
||||
let mut options = formatter::Options::default();
|
||||
if let Some(cfg_opts) = config.global_options.formatting.clone() {
|
||||
options.update(cfg_opts);
|
||||
}
|
||||
options.update_camel(camel_opts);
|
||||
|
||||
let syntax = parse(toml);
|
||||
let error_ranges = syntax.errors.iter().map(|e| e.range).collect::<Vec<_>>();
|
||||
|
||||
Ok(formatter::format_with_path_scopes(
|
||||
syntax.into_dom(),
|
||||
options,
|
||||
&error_ranges,
|
||||
config.format_scopes(Path::new("")),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn lint(env: JsValue, toml: String, config: JsValue) -> Result<JsValue, JsError> {
|
||||
let mut config = if config.is_undefined() {
|
||||
Config::default()
|
||||
} else {
|
||||
serde_wasm_bindgen::from_value(config)?
|
||||
};
|
||||
let env = WasmEnvironment::from(env);
|
||||
config
|
||||
.prepare(&env, Path::new("/"))
|
||||
.map_err(|err| JsError::new(&err.to_string()))?;
|
||||
|
||||
let syntax = parse(&toml);
|
||||
|
||||
if !syntax.errors.is_empty() {
|
||||
return Ok(serde_wasm_bindgen::to_value(&LintResult {
|
||||
errors: syntax
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| LintError {
|
||||
range: Range {
|
||||
start: err.range.start().into(),
|
||||
end: err.range.end().into(),
|
||||
}
|
||||
.into(),
|
||||
error: err.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})?);
|
||||
}
|
||||
|
||||
let dom = syntax.into_dom();
|
||||
|
||||
if let Err(errors) = dom.validate() {
|
||||
return Ok(serde_wasm_bindgen::to_value(&LintResult {
|
||||
errors: errors
|
||||
.map(|err| LintError {
|
||||
range: None,
|
||||
error: err.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})?);
|
||||
}
|
||||
|
||||
let schemas = Schemas::new(env, Default::default());
|
||||
schemas.associations().add_from_config(&config);
|
||||
|
||||
if let Some(schema) = schemas
|
||||
.associations()
|
||||
.association_for(&Url::parse("file:///__.toml").unwrap())
|
||||
{
|
||||
let schema_errors = schemas
|
||||
.validate(&schema.url, &serde_json::to_value(&dom).unwrap())
|
||||
.await
|
||||
.map_err(|err| JsError::new(&err.to_string()))?;
|
||||
|
||||
return Ok(serde_wasm_bindgen::to_value(&LintResult {
|
||||
errors: schema_errors
|
||||
.into_iter()
|
||||
.map(|err| LintError {
|
||||
range: None,
|
||||
error: err.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})?);
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn to_json(toml: &str) -> Result<String, JsError> {
|
||||
let syntax = parse(toml);
|
||||
|
||||
if !syntax.errors.is_empty() {
|
||||
return Err(JsError::new("the given input contains syntax errors"));
|
||||
}
|
||||
|
||||
let dom = syntax.into_dom();
|
||||
|
||||
if dom.validate().is_err() {
|
||||
return Err(JsError::new("the given input contains errors"));
|
||||
}
|
||||
|
||||
Ok(serde_json::to_string(&dom).unwrap())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn from_json(json: &str) -> Result<String, JsError> {
|
||||
let dom: Node = serde_json::from_str(json)?;
|
||||
Ok(dom.to_toml(false))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn run_cli(env: JsValue, args: JsValue) -> Result<(), JsError> {
|
||||
use clap::Parser;
|
||||
use environment::WasmEnvironment;
|
||||
use taplo_cli::{
|
||||
args::{Colors, TaploArgs},
|
||||
Taplo,
|
||||
};
|
||||
use taplo_common::{environment::Environment, log::setup_stderr_logging};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::Instrument;
|
||||
|
||||
let env = WasmEnvironment::from(env);
|
||||
let args: Vec<String> = serde_wasm_bindgen::from_value(args)?;
|
||||
|
||||
let cli = match TaploArgs::try_parse_from(args) {
|
||||
Ok(v) => v,
|
||||
Err(error) => {
|
||||
env.stdout().write_all(error.to_string().as_bytes()).await?;
|
||||
return Err(JsError::new("operation failed"));
|
||||
}
|
||||
};
|
||||
|
||||
setup_stderr_logging(
|
||||
env.clone(),
|
||||
cli.log_spans,
|
||||
cli.verbose,
|
||||
match cli.colors {
|
||||
Colors::Auto => None,
|
||||
Colors::Always => Some(true),
|
||||
Colors::Never => Some(false),
|
||||
},
|
||||
);
|
||||
|
||||
match Taplo::new(env.clone())
|
||||
.execute(cli)
|
||||
.instrument(tracing::info_span!("taplo"))
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => {
|
||||
tracing::error!(error = %format!("{error:#}"), "operation failed");
|
||||
Err(JsError::new("operation failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "lsp")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn create_lsp(env: JsValue, lsp_interface: JsValue) -> lsp::TaploWasmLsp {
|
||||
use taplo_common::log::setup_stderr_logging;
|
||||
|
||||
let env = WasmEnvironment::from(env);
|
||||
setup_stderr_logging(env.clone(), false, false, None);
|
||||
|
||||
lsp::TaploWasmLsp {
|
||||
server: taplo_lsp::create_server(),
|
||||
world: taplo_lsp::create_world(env),
|
||||
lsp_interface: lsp::WasmLspInterface::from(lsp_interface),
|
||||
}
|
||||
}
|
85
crates/taplo-wasm/src/lsp.rs
Normal file
85
crates/taplo-wasm/src/lsp.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::environment::WasmEnvironment;
|
||||
use futures::Sink;
|
||||
use js_sys::Function;
|
||||
use lsp_async_stub::{rpc, Server};
|
||||
use std::{io, sync::Arc};
|
||||
use taplo_lsp::world::WorldState;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct TaploWasmLsp {
|
||||
pub(crate) server: Server<Arc<WorldState<WasmEnvironment>>>,
|
||||
pub(crate) world: Arc<WorldState<WasmEnvironment>>,
|
||||
pub(crate) lsp_interface: WasmLspInterface,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl TaploWasmLsp {
|
||||
pub fn send(&self, message: JsValue) -> Result<(), JsError> {
|
||||
let message: lsp_async_stub::rpc::Message = serde_wasm_bindgen::from_value(message)?;
|
||||
let world = self.world.clone();
|
||||
let writer = self.lsp_interface.clone();
|
||||
|
||||
let msg_fut = self.server.handle_message(world, message, writer);
|
||||
|
||||
spawn_local(async move {
|
||||
if let Err(err) = msg_fut.await {
|
||||
tracing::error!(error = %err, "lsp message error");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WasmLspInterface {
|
||||
js_on_message: Function,
|
||||
}
|
||||
|
||||
impl From<JsValue> for WasmLspInterface {
|
||||
fn from(val: JsValue) -> Self {
|
||||
Self {
|
||||
js_on_message: js_sys::Reflect::get(&val, &JsValue::from_str("js_now"))
|
||||
.unwrap()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<rpc::Message> for WasmLspInterface {
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll_ready(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
message: rpc::Message,
|
||||
) -> Result<(), Self::Error> {
|
||||
let this = JsValue::null();
|
||||
self.js_on_message
|
||||
.call1(&this, &serde_wasm_bindgen::to_value(&message).unwrap())
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
8
editors/vscode/.gitignore
vendored
8
editors/vscode/.gitignore
vendored
|
@ -4,3 +4,11 @@ node_modules
|
|||
.vscode-test/
|
||||
*.vsix
|
||||
.vscode
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
node_modules
|
||||
|
|
|
@ -11,4 +11,7 @@ tsconfig.json
|
|||
tslint.json
|
||||
sample
|
||||
images
|
||||
node_modules
|
||||
node_modules
|
||||
.yarn/**
|
||||
.yarnrc.yml
|
||||
rollup-config.js
|
||||
|
|
786
editors/vscode/.yarn/releases/yarn-3.2.1.cjs
vendored
Executable file
786
editors/vscode/.yarn/releases/yarn-3.2.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
1
editors/vscode/.yarnrc.yml
Normal file
1
editors/vscode/.yarnrc.yml
Normal file
|
@ -0,0 +1 @@
|
|||
nodeLinker: node-modules
|
|
@ -1,5 +1,20 @@
|
|||
# Change Log
|
||||
|
||||
## next
|
||||
|
||||
### Features
|
||||
|
||||
- Support for `tomlValidation` for schemas.
|
||||
- Wide-range JSON schema support
|
||||
- Support for schema store
|
||||
- Support for multiple workspaces
|
||||
- It is now possible to specify schemas with either `#:schema <URL>` or `$schema = "<URL>"` in TOML files
|
||||
- Added rename feature that lets you rename keys that appear at multiple locations
|
||||
|
||||
### Other
|
||||
|
||||
- Almost a complete internal rewrite with various fixes and improvements. ([#211](https://github.com/tamasfe/taplo/pull/211))
|
||||
|
||||
## 0.14.3
|
||||
|
||||
### Fixes
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"engines": {
|
||||
"vscode": "^1.44.0"
|
||||
"vscode": "^1.68.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages",
|
||||
|
@ -158,8 +158,14 @@
|
|||
"configuration": {
|
||||
"title": "Even Better TOML",
|
||||
"properties": {
|
||||
"evenBetterToml.taplo.bundled": {
|
||||
"description": "Use the bundled taplo language server. If set to `false`, the `taplo` executable must be found in PATH or must be set in `evenBetterToml.taplo.path`.",
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true
|
||||
},
|
||||
"evenBetterToml.taplo.path": {
|
||||
"description": "An absolute path to the `taplo` executable.",
|
||||
"description": "An absolute path to the `taplo` executable. `evenBetterToml.taplo.bundled` needs to be set to `false` for this to have any effect.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
|
@ -172,7 +178,7 @@
|
|||
"object"
|
||||
],
|
||||
"scope": "resource",
|
||||
"description": "Environment variables for the Taplo LSP executable.",
|
||||
"description": "Environment variables for Taplo.",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -184,7 +190,7 @@
|
|||
"null"
|
||||
],
|
||||
"scope": "resource",
|
||||
"description": "Additional arguments for the Taplo LSP executable.",
|
||||
"description": "Additional arguments for Taplo. Has no effect for the bundled LSP.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -272,25 +278,29 @@
|
|||
"main": "./dist/extension.js",
|
||||
"scripts": {
|
||||
"build:syntax": "ts-node src/syntax/index.ts",
|
||||
"vscode:prepublish": "yarn --ignore-engines build",
|
||||
"build": "yarn --ignore-engines rollup --silent -c rollup.config.js"
|
||||
"vscode:prepublish": "yarn build",
|
||||
"build": "rm -rf dist && rollup -c rollup.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"deep-equal": "^2.0.4",
|
||||
"@taplo/lsp": "^0.3.0-alpha.4",
|
||||
"deep-equal": "^2.0.5",
|
||||
"encoding": "^0.1.13",
|
||||
"vscode-languageclient": "^7.0.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"node-fetch": "^3.2.6",
|
||||
"vscode-languageclient": "^8.0.1",
|
||||
"which": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^16.0.0",
|
||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/node": "^12.12.0",
|
||||
"@types/vscode": "^1.44.0",
|
||||
"@types/node": "^17.0.42",
|
||||
"@types/vscode": "^1.68.0",
|
||||
"@types/which": "^2.0.1",
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-typescript2": "^0.29.0",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^4.0.5"
|
||||
"rollup": "^2.75.6",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.32.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,51 @@
|
|||
import typescript from "rollup-plugin-typescript2";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import path from "node:path";
|
||||
|
||||
export default {
|
||||
const onwarn = (warning, rollupWarn) => {
|
||||
const ignoredWarnings = [
|
||||
{
|
||||
ignoredCode: "CIRCULAR_DEPENDENCY",
|
||||
ignoredPath: "node_modules/semver",
|
||||
},
|
||||
];
|
||||
|
||||
// only show warning when code and path don't match
|
||||
// anything in above list of ignored warnings
|
||||
if (
|
||||
!ignoredWarnings.some(
|
||||
({ ignoredCode, ignoredPath }) =>
|
||||
warning.code === ignoredCode &&
|
||||
warning.importer.includes(path.normalize(ignoredPath))
|
||||
)
|
||||
) {
|
||||
rollupWarn(warning);
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('rollup').RollupOptions} */
|
||||
const options = {
|
||||
onwarn,
|
||||
input: {
|
||||
// server: "src/server.ts",
|
||||
server: "src/server.ts",
|
||||
extension: "src/extension.ts",
|
||||
},
|
||||
output: {
|
||||
sourcemap: false,
|
||||
format: "cjs",
|
||||
dir: "dist",
|
||||
chunkFileNames: "[name].js",
|
||||
},
|
||||
external: ["vscode"],
|
||||
preserveEntrySignatures: true,
|
||||
plugins: [
|
||||
commonjs(),
|
||||
resolve({ preferBuiltins: true }),
|
||||
typescript(),
|
||||
commonjs({ include: ["src/*.ts", "node_modules/**", "../lsp/**"] }),
|
||||
resolve({ jsnext: true, preferBuiltins: true }),
|
||||
terser(),
|
||||
],
|
||||
};
|
||||
|
||||
export default options;
|
||||
|
|
|
@ -1,55 +1,71 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as client from "vscode-languageclient/node";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import which from "which";
|
||||
import { getOutput } from "./extension";
|
||||
import { getOutput } from "./util";
|
||||
|
||||
export async function createClient(
|
||||
context: vscode.ExtensionContext
|
||||
): Promise<client.LanguageClient> {
|
||||
return createNativeClient(context);
|
||||
}
|
||||
const out = getOutput();
|
||||
|
||||
async function createNativeClient(
|
||||
context: vscode.ExtensionContext
|
||||
): Promise<client.LanguageClient> {
|
||||
const taploPath =
|
||||
vscode.workspace.getConfiguration().get("evenBetterToml.taplo.path") ??
|
||||
which.sync("taplo", { nothrow: true });
|
||||
|
||||
if (typeof taploPath !== "string") {
|
||||
getOutput().appendLine("failed to locate Taplo LSP");
|
||||
throw new Error("failed to locate Taplo LSP");
|
||||
}
|
||||
|
||||
let extraArgs = vscode.workspace
|
||||
const bundled = !!vscode.workspace
|
||||
.getConfiguration()
|
||||
.get("evenBetterToml.taplo.extraArgs");
|
||||
.get("evenBetterToml.taplo.bundled");
|
||||
|
||||
if (!Array.isArray(extraArgs)) {
|
||||
extraArgs = [];
|
||||
let serverOpts: client.ServerOptions;
|
||||
if (bundled) {
|
||||
const taploPath = context.asAbsolutePath(path.join("dist", "server.js"));
|
||||
|
||||
const run: client.NodeModule = {
|
||||
module: taploPath,
|
||||
transport: client.TransportKind.ipc,
|
||||
};
|
||||
|
||||
serverOpts = {
|
||||
run,
|
||||
debug: run,
|
||||
};
|
||||
} else {
|
||||
const taploPath =
|
||||
vscode.workspace.getConfiguration().get("evenBetterToml.taplo.path") ??
|
||||
which.sync("taplo", { nothrow: true });
|
||||
|
||||
if (typeof taploPath !== "string") {
|
||||
out.appendLine("failed to locate Taplo LSP");
|
||||
throw new Error("failed to locate Taplo LSP");
|
||||
}
|
||||
|
||||
let extraArgs = vscode.workspace
|
||||
.getConfiguration()
|
||||
.get("evenBetterToml.taplo.extraArgs");
|
||||
|
||||
if (!Array.isArray(extraArgs)) {
|
||||
extraArgs = [];
|
||||
}
|
||||
|
||||
const args: string[] = (extraArgs as any[]).filter(
|
||||
a => typeof a === "string"
|
||||
);
|
||||
|
||||
const run: client.Executable = {
|
||||
command: taploPath,
|
||||
args: ["lsp", "stdio", ...args],
|
||||
options: {
|
||||
env:
|
||||
vscode.workspace
|
||||
.getConfiguration()
|
||||
.get("evenBetterToml.taplo.environment") ?? undefined,
|
||||
},
|
||||
};
|
||||
|
||||
serverOpts = {
|
||||
run,
|
||||
debug: run,
|
||||
};
|
||||
}
|
||||
|
||||
const args: string[] = (extraArgs as any[]).filter(
|
||||
a => typeof a === "string"
|
||||
);
|
||||
|
||||
const run: client.Executable = {
|
||||
command: taploPath,
|
||||
args: ["lsp", "stdio", ...args],
|
||||
options: {
|
||||
env:
|
||||
vscode.workspace
|
||||
.getConfiguration()
|
||||
.get("evenBetterToml.taplo.environment") ?? undefined,
|
||||
},
|
||||
};
|
||||
|
||||
let serverOpts: client.ServerOptions = {
|
||||
run,
|
||||
debug: run,
|
||||
};
|
||||
|
||||
return new client.LanguageClient(
|
||||
"evenBetterToml",
|
||||
"Even Better TOML LSP",
|
||||
|
|
|
@ -1,41 +1,54 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as client from "vscode-languageclient/node";
|
||||
import { getOutput } from "../extension";
|
||||
import { getOutput } from "../util";
|
||||
|
||||
// FIXME: this could be a bit more DRY.
|
||||
export function register(
|
||||
ctx: vscode.ExtensionContext,
|
||||
c: client.LanguageClient
|
||||
) {
|
||||
c.onReady().then(() => {
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.copyAsJson",
|
||||
async editor => {
|
||||
const document = editor.document;
|
||||
// Avoid accidental copying of nothing
|
||||
if (editor.selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.copyAsJson",
|
||||
async editor => {
|
||||
const document = editor.document;
|
||||
// Avoid accidental copying of nothing
|
||||
if (editor.selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedText = document.getText(editor.selection);
|
||||
// Avoid accidental copying of nothing
|
||||
if (selectedText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
const selectedText = document.getText(editor.selection);
|
||||
// Avoid accidental copying of nothing
|
||||
if (selectedText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToJson",
|
||||
{
|
||||
text: selectedText,
|
||||
}
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToJson",
|
||||
{
|
||||
text: selectedText,
|
||||
}
|
||||
);
|
||||
|
||||
const out = getOutput();
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert TOML to JSON: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
const out = getOutput();
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert TOML to JSON: ${res.error}`);
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!res.text) {
|
||||
out.appendLine(`The response shouldn't be empty, but it is.`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
|
@ -46,23 +59,64 @@ export function register(
|
|||
}
|
||||
return;
|
||||
}
|
||||
await vscode.env.clipboard.writeText(res.text);
|
||||
} catch (e) {
|
||||
out.appendLine(`Couldn't write to clipboard: ${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
try {
|
||||
if (!res.text) {
|
||||
out.appendLine(`The response shouldn't be empty, but it is.`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
await vscode.env.clipboard.writeText(res.text);
|
||||
} catch (e) {
|
||||
out.appendLine(`Couldn't write to clipboard: ${e}`);
|
||||
await vscode.window.showInformationMessage("JSON copied!");
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.copyAsToml",
|
||||
async editor => {
|
||||
const document = editor.document;
|
||||
// Avoid accidental copying of nothing
|
||||
if (editor.selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedText = document.getText(editor.selection);
|
||||
// Avoid accidental copying of nothing
|
||||
if (selectedText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToToml",
|
||||
{
|
||||
text: selectedText,
|
||||
}
|
||||
);
|
||||
|
||||
const out = getOutput();
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert JSON to TOML: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!res.text) {
|
||||
out.appendLine(`The response shouldn't be empty, but it is.`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
|
@ -73,170 +127,114 @@ export function register(
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.showInformationMessage("JSON copied!");
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.copyAsToml",
|
||||
async editor => {
|
||||
const document = editor.document;
|
||||
// Avoid accidental copying of nothing
|
||||
if (editor.selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedText = document.getText(editor.selection);
|
||||
// Avoid accidental copying of nothing
|
||||
if (selectedText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToToml",
|
||||
{
|
||||
text: selectedText,
|
||||
}
|
||||
await vscode.env.clipboard.writeText(res.text);
|
||||
} catch (e) {
|
||||
out.appendLine(`Couldn't write to clipboard: ${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
const out = getOutput();
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert JSON to TOML: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!res.text) {
|
||||
out.appendLine(`The response shouldn't be empty, but it is.`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
await vscode.env.clipboard.writeText(res.text);
|
||||
} catch (e) {
|
||||
out.appendLine(`Couldn't write to clipboard: ${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Copying has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.showInformationMessage("TOML copied!");
|
||||
return;
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.pasteAsJson",
|
||||
async editor => {
|
||||
const out = getOutput();
|
||||
let input: string;
|
||||
try {
|
||||
input = await vscode.env.clipboard.readText();
|
||||
} catch (e) {
|
||||
out.appendLine(`Failed to read from clipboard:${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToJson",
|
||||
{
|
||||
text: input,
|
||||
}
|
||||
await vscode.window.showInformationMessage("TOML copied!");
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.pasteAsJson",
|
||||
async editor => {
|
||||
const out = getOutput();
|
||||
let input: string;
|
||||
try {
|
||||
input = await vscode.env.clipboard.readText();
|
||||
} catch (e) {
|
||||
out.appendLine(`Failed to read from clipboard:${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert to JSON: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Pasting JSON has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
|
||||
editor.edit(e => {
|
||||
e.replace(editor.selection, res.text!);
|
||||
});
|
||||
return;
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.pasteAsToml",
|
||||
async editor => {
|
||||
const out = getOutput();
|
||||
let input: string;
|
||||
try {
|
||||
input = await vscode.env.clipboard.readText();
|
||||
} catch (e) {
|
||||
out.appendLine(`Failed to read from clipboard:${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToJson",
|
||||
{
|
||||
text: input,
|
||||
}
|
||||
);
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToToml",
|
||||
{
|
||||
text: input,
|
||||
}
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert to JSON: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Pasting JSON has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert to TOML: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
|
||||
editor.edit(e => {
|
||||
e.replace(editor.selection, res.text!);
|
||||
});
|
||||
return;
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
editor.edit(e => {
|
||||
e.replace(editor.selection, res.text!);
|
||||
});
|
||||
}
|
||||
),
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.pasteAsToml",
|
||||
async editor => {
|
||||
const out = getOutput();
|
||||
let input: string;
|
||||
try {
|
||||
input = await vscode.env.clipboard.readText();
|
||||
} catch (e) {
|
||||
out.appendLine(`Failed to read from clipboard:${e}`);
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await c.sendRequest<{ text?: string; error?: string }>(
|
||||
"taplo/convertToToml",
|
||||
{
|
||||
text: input,
|
||||
}
|
||||
);
|
||||
|
||||
if (res.error?.length ?? 0 !== 0) {
|
||||
out.appendLine(`Failed to convert to TOML: ${res.error}`);
|
||||
|
||||
const show = await vscode.window.showErrorMessage(
|
||||
"Paste from clipboard has failed!",
|
||||
"Show Details"
|
||||
);
|
||||
|
||||
if (show) {
|
||||
out.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
editor.edit(e => {
|
||||
e.replace(editor.selection, res.text!);
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,58 +1,55 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as client from "vscode-languageclient/node";
|
||||
import { getOutput } from "../extension";
|
||||
|
||||
export function register(
|
||||
ctx: vscode.ExtensionContext,
|
||||
c: client.LanguageClient
|
||||
) {
|
||||
c.onReady().then(() => {
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.selectSchema",
|
||||
async editor => {
|
||||
const schemasResp: { schemas: { url: string; meta?: any }[] } =
|
||||
await c.sendRequest("taplo/listSchemas", {
|
||||
documentUri: editor.document.uri.toString(),
|
||||
});
|
||||
|
||||
interface SchemaItem extends vscode.QuickPickItem {
|
||||
url: string;
|
||||
meta?: Record<string, any>;
|
||||
}
|
||||
|
||||
const selectedSchema: { schema?: { url: string } } =
|
||||
await c.sendRequest("taplo/associatedSchema", {
|
||||
documentUri: editor.document.uri.toString(),
|
||||
});
|
||||
|
||||
const selection = await vscode.window.showQuickPick<SchemaItem>(
|
||||
schemasResp.schemas.map(s => ({
|
||||
label: s.meta?.name ?? s.url,
|
||||
description: schemaDescription(s.meta),
|
||||
detail: schemaDetails(s.url, s.meta),
|
||||
picked: selectedSchema.schema?.url === s.url,
|
||||
url: s.url,
|
||||
meta: s.meta,
|
||||
}))
|
||||
);
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
c.sendNotification("taplo/associateSchema", {
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerTextEditorCommand(
|
||||
"evenBetterToml.selectSchema",
|
||||
async editor => {
|
||||
const schemasResp: { schemas: { url: string; meta?: any }[] } =
|
||||
await c.sendRequest("taplo/listSchemas", {
|
||||
documentUri: editor.document.uri.toString(),
|
||||
schemaUri: selection.url,
|
||||
rule: {
|
||||
url: editor.document.uri.toString(),
|
||||
},
|
||||
meta: selection.meta,
|
||||
});
|
||||
|
||||
interface SchemaItem extends vscode.QuickPickItem {
|
||||
url: string;
|
||||
meta?: Record<string, any>;
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const selectedSchema: { schema?: { url: string } } =
|
||||
await c.sendRequest("taplo/associatedSchema", {
|
||||
documentUri: editor.document.uri.toString(),
|
||||
});
|
||||
|
||||
const selection = await vscode.window.showQuickPick<SchemaItem>(
|
||||
schemasResp.schemas.map(s => ({
|
||||
label: s.meta?.name ?? s.url,
|
||||
description: schemaDescription(s.meta),
|
||||
detail: schemaDetails(s.url, s.meta),
|
||||
picked: selectedSchema.schema?.url === s.url,
|
||||
url: s.url,
|
||||
meta: s.meta,
|
||||
}))
|
||||
);
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
c.sendNotification("taplo/associateSchema", {
|
||||
documentUri: editor.document.uri.toString(),
|
||||
schemaUri: selection.url,
|
||||
rule: {
|
||||
url: editor.document.uri.toString(),
|
||||
},
|
||||
meta: selection.meta,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function schemaDescription(meta: any | undefined): string | undefined {
|
||||
|
|
|
@ -3,16 +3,7 @@ import * as client from "vscode-languageclient/node";
|
|||
import { registerCommands } from "./commands";
|
||||
import { createClient } from "./client";
|
||||
import { syncExtensionSchemas } from "./tomlValidation";
|
||||
|
||||
let output: vscode.OutputChannel;
|
||||
|
||||
export function getOutput(): vscode.OutputChannel {
|
||||
if (!output) {
|
||||
output = vscode.window.createOutputChannel("Even Better TOML");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
import { getOutput } from "./util";
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const schemaIndicator = vscode.window.createStatusBarItem(
|
||||
|
@ -25,9 +16,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
schemaIndicator.command = "evenBetterToml.selectSchema";
|
||||
|
||||
const c = await createClient(context);
|
||||
context.subscriptions.push(c.start());
|
||||
|
||||
await c.onReady();
|
||||
await c.start();
|
||||
|
||||
if (vscode.window.activeTextEditor?.document.languageId === "toml") {
|
||||
schemaIndicator.show();
|
||||
|
@ -37,7 +26,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
syncExtensionSchemas(context, c);
|
||||
|
||||
context.subscriptions.push(
|
||||
output,
|
||||
getOutput(),
|
||||
schemaIndicator,
|
||||
c.onNotification("taplo/messageWithOutput", async params =>
|
||||
showMessage(params, c)
|
||||
|
|
63
editors/vscode/src/server.ts
Normal file
63
editors/vscode/src/server.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import fs from "fs";
|
||||
import fsPromise from "fs/promises";
|
||||
import path from "path";
|
||||
import { exit } from "process";
|
||||
import { RpcMessage, TaploLsp } from "@taplo/lsp";
|
||||
import fetch, { Headers, Request, Response } from "node-fetch";
|
||||
import glob from "fast-glob";
|
||||
|
||||
let taplo: TaploLsp;
|
||||
|
||||
process.on("message", async (d: RpcMessage) => {
|
||||
if (d.method === "exit") {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (typeof taplo === "undefined") {
|
||||
taplo = await TaploLsp.initialize(
|
||||
{
|
||||
cwd: () => process.cwd(),
|
||||
envVar: name => process.env[name],
|
||||
findConfigFile: from => {
|
||||
const fileNames = [".taplo.toml", "taplo.toml"];
|
||||
|
||||
for (const name of fileNames) {
|
||||
try {
|
||||
const fullPath = path.join(from, name);
|
||||
fs.accessSync(fullPath);
|
||||
return fullPath;
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
glob: p => glob.sync(p),
|
||||
isAbsolute: p => path.isAbsolute(p),
|
||||
now: () => new Date(),
|
||||
readFile: path => fsPromise.readFile(path),
|
||||
writeFile: (path, content) => fsPromise.writeFile(path, content),
|
||||
stderr: process.stderr,
|
||||
stdErrAtty: () => process.stderr.isTTY,
|
||||
stdin: process.stdin,
|
||||
stdout: process.stdout,
|
||||
urlToFilePath: (url: string) => decodeURI(url).slice("file://".length),
|
||||
fetch: {
|
||||
fetch,
|
||||
Headers,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
},
|
||||
{
|
||||
onMessage(message) {
|
||||
process.send(message);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
taplo.send(d);
|
||||
});
|
||||
|
||||
// These are panics from Rust.
|
||||
process.on("unhandledRejection", up => {
|
||||
throw up;
|
||||
});
|
|
@ -1,5 +1,15 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
let output: vscode.OutputChannel;
|
||||
|
||||
export function getOutput(): vscode.OutputChannel {
|
||||
if (!output) {
|
||||
output = vscode.window.createOutputChannel("Even Better TOML");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function allRange(doc: vscode.TextDocument): vscode.Range {
|
||||
let firstLine = doc.lineAt(0);
|
||||
let lastLine = doc.lineAt(doc.lineCount - 1);
|
||||
|
|
File diff suppressed because it is too large
Load diff
8
js/.gitignore
vendored
Normal file
8
js/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
node_modules
|
786
js/.yarn/releases/yarn-stable-temp.cjs
vendored
Executable file
786
js/.yarn/releases/yarn-stable-temp.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
2
js/.yarnrc.yml
Normal file
2
js/.yarnrc.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
nodeLinker: node-modules
|
||||
npmPublishAccess: public
|
5
js/cli/.gitignore
vendored
Normal file
5
js/cli/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
dist
|
||||
node_modules
|
||||
target
|
||||
**/.local*
|
||||
*.tgz
|
8
js/cli/.npmignore
Normal file
8
js/cli/.npmignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
src
|
||||
target
|
||||
util
|
||||
**/.local*
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
rollup.config.js
|
||||
yarn-error.log
|
3
js/cli/README.md
Normal file
3
js/cli/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
This is a JavaScript wrapper for the Taplo TOML CLI tool.
|
||||
|
||||
All the documentation and information is available on the [website](https://taplo.tamasfe.dev).
|
34
js/cli/package.json
Normal file
34
js/cli/package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@taplo/cli",
|
||||
"version": "0.4.0",
|
||||
"description": "A TOML utility tool.",
|
||||
"bin": {
|
||||
"taplo": "dist/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn rollup --silent -c rollup.config.js && node util/add_shebang.js",
|
||||
"prepublish": "RELEASE=true yarn build"
|
||||
},
|
||||
"homepage": "https://taplo.tamasfe.dev",
|
||||
"repository": "https://github.com/tamasfe/taplo",
|
||||
"author": {
|
||||
"name": "tamasfe",
|
||||
"url": "https://github.com/tamasfe"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@taplo/core": "^0.1.0",
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@types/node": "^17.0.41",
|
||||
"@types/node-fetch": "^2.6.1",
|
||||
"@wasm-tool/rollup-plugin-rust": "^2.2.2",
|
||||
"fast-glob": "^3.2.11",
|
||||
"node-fetch": "^3.2.6",
|
||||
"rollup": "^2.75.6",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.32.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
36
js/cli/rollup.config.js
Normal file
36
js/cli/rollup.config.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import rust from "@wasm-tool/rollup-plugin-rust";
|
||||
import typescript from "rollup-plugin-typescript2";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
export default {
|
||||
input: {
|
||||
cli: "src/cli.ts",
|
||||
},
|
||||
output: {
|
||||
sourcemap: false,
|
||||
format: "cjs",
|
||||
dir: "dist",
|
||||
},
|
||||
preserveEntrySignatures: false,
|
||||
inlineDynamicImports: true,
|
||||
plugins: [
|
||||
rust({
|
||||
debug: process.env["RELEASE"] !== "true",
|
||||
nodejs: true,
|
||||
inlineWasm: true,
|
||||
cargoArgs: ["--features", "cli"],
|
||||
}),
|
||||
commonjs(),
|
||||
resolve({
|
||||
jsnext: true,
|
||||
preferBuiltins: true,
|
||||
rootDir: path.join(process.cwd(), ".."),
|
||||
}),
|
||||
typescript(),
|
||||
terser(),
|
||||
],
|
||||
};
|
52
js/cli/src/cli.ts
Normal file
52
js/cli/src/cli.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import fsPromise from "fs/promises";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
import path from "path";
|
||||
import glob from "fast-glob";
|
||||
import fetch, { Headers, Request, Response } from "node-fetch";
|
||||
import loadTaplo from "../../../crates/taplo-wasm/Cargo.toml";
|
||||
import { convertEnv, Environment, prepareEnv } from "@taplo/core";
|
||||
|
||||
(async function main() {
|
||||
const taplo = await loadTaplo();
|
||||
taplo.initialize();
|
||||
|
||||
const env: Environment = {
|
||||
cwd: () => process.cwd(),
|
||||
envVar: name => process.env[name],
|
||||
findConfigFile: from => {
|
||||
try {
|
||||
fs.accessSync(path.join(from, ".taplo.toml"));
|
||||
return path.join(from, ".taplo.toml");
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
fs.accessSync(path.join(from, "taplo.toml"));
|
||||
return path.join(from, "taplo.toml");
|
||||
} catch {}
|
||||
},
|
||||
glob: p => glob.sync(p),
|
||||
isAbsolute: p => path.isAbsolute(p),
|
||||
now: () => new Date(),
|
||||
readFile: path => fsPromise.readFile(path),
|
||||
writeFile: (path, content) => fsPromise.writeFile(path, content),
|
||||
stderr: process.stderr,
|
||||
stdErrAtty: () => process.stderr.isTTY,
|
||||
stdin: process.stdin,
|
||||
stdout: process.stdout,
|
||||
urlToFilePath: (url: string) => decodeURI(url).slice("file://".length),
|
||||
fetch: {
|
||||
fetch,
|
||||
Headers,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
};
|
||||
prepareEnv(env);
|
||||
|
||||
try {
|
||||
await taplo.run_cli(convertEnv(env), process.argv.slice(1));
|
||||
} catch (err) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
})();
|
4
js/cli/src/rust.d.ts
vendored
Normal file
4
js/cli/src/rust.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module "*/Cargo.toml" {
|
||||
const mod: any;
|
||||
export default mod;
|
||||
}
|
13
js/cli/tsconfig.json
Normal file
13
js/cli/tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"lib": ["ES2019"],
|
||||
"sourceMap": false,
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
6
js/cli/util/add_shebang.js
Normal file
6
js/cli/util/add_shebang.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
var fs = require('fs');
|
||||
var path = "dist/cli.js";
|
||||
var data = "#!/usr/bin/env node\n\n";
|
||||
data += fs.readFileSync(path);
|
||||
fs.writeFileSync(path, data);
|
5
js/lib/.gitignore
vendored
Normal file
5
js/lib/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
dist
|
||||
node_modules
|
||||
target
|
||||
**/.local*
|
||||
*.tgz
|
8
js/lib/.npmignore
Normal file
8
js/lib/.npmignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
src
|
||||
target
|
||||
util
|
||||
**/.local*
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
rollup.config.js
|
||||
yarn-error.log
|
3
js/lib/README.md
Normal file
3
js/lib/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
This is a JavaScript wrapper for the Taplo TOML library.
|
||||
|
||||
All the documentation and information is available on the [website](https://taplo.tamasfe.dev).
|
33
js/lib/package.json
Normal file
33
js/lib/package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "@taplo/lib",
|
||||
"version": "0.4.0-alpha.2",
|
||||
"description": "A TOML linter and formatter and utility library.",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "yarn rollup --silent -c rollup.config.js",
|
||||
"prepublish": "RELEASE=true yarn build"
|
||||
},
|
||||
"homepage": "https://taplo.tamasfe.dev",
|
||||
"main": "dist/index.js",
|
||||
"repository": "https://github.com/tamasfe/taplo",
|
||||
"author": {
|
||||
"name": "tamasfe",
|
||||
"url": "https://github.com/tamasfe"
|
||||
},
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"@taplo/core": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.41",
|
||||
"@wasm-tool/rollup-plugin-rust": "^2.2.2",
|
||||
"rollup": "^2.75.6",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.32.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.7.3",
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0"
|
||||
}
|
||||
}
|
34
js/lib/rollup.config.js
Normal file
34
js/lib/rollup.config.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import rust from "@wasm-tool/rollup-plugin-rust";
|
||||
import typescript from "rollup-plugin-typescript2";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
export default {
|
||||
input: {
|
||||
index: "src/index.ts",
|
||||
},
|
||||
output: {
|
||||
sourcemap: false,
|
||||
name: "taplo",
|
||||
format: "umd",
|
||||
dir: "dist",
|
||||
},
|
||||
plugins: [
|
||||
rust({
|
||||
debug: process.env["RELEASE"] !== "true",
|
||||
nodejs: true,
|
||||
inlineWasm: true,
|
||||
}),
|
||||
commonjs(),
|
||||
resolve({
|
||||
jsnext: true,
|
||||
preferBuiltins: true,
|
||||
rootDir: path.join(process.cwd(), ".."),
|
||||
}),
|
||||
typescript(),
|
||||
terser(),
|
||||
],
|
||||
};
|
241
js/lib/src/index.ts
Normal file
241
js/lib/src/index.ts
Normal file
|
@ -0,0 +1,241 @@
|
|||
import {
|
||||
Config,
|
||||
convertEnv,
|
||||
Environment,
|
||||
FormatterOptions,
|
||||
prepareEnv,
|
||||
} from "@taplo/core";
|
||||
import loadTaplo from "../../../crates/taplo-wasm/Cargo.toml";
|
||||
import { objectCamel } from "./util";
|
||||
|
||||
/**
|
||||
* Options for the format function.
|
||||
*/
|
||||
export interface FormatOptions {
|
||||
/**
|
||||
* Options to pass to the formatter.
|
||||
*/
|
||||
options?: FormatterOptions;
|
||||
|
||||
/**
|
||||
* Taplo configuration, this can be parsed
|
||||
* from files like `taplo.toml` or provided manually.
|
||||
*/
|
||||
config?: Config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for TOML Lint.
|
||||
*/
|
||||
export interface LintOptions {
|
||||
/**
|
||||
* Taplo configuration, this can be parsed
|
||||
* from `.taplo.toml` or provided manually.
|
||||
*/
|
||||
config?: Config;
|
||||
}
|
||||
/**
|
||||
* An lint error.
|
||||
*/
|
||||
export interface LintError {
|
||||
/**
|
||||
* A range within the TOML document if any.
|
||||
*/
|
||||
range?: Range;
|
||||
/**
|
||||
* The error message.
|
||||
*/
|
||||
error: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The object returned from the lint function.
|
||||
*/
|
||||
export interface LintResult {
|
||||
/**
|
||||
* Lint errors, if any.
|
||||
*
|
||||
* This includes syntax, semantic and schema errors as well.
|
||||
*/
|
||||
errors: Array<LintError>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class allows for usage of the library in a synchronous context
|
||||
* after being asynchronously initialized once.
|
||||
*
|
||||
* It cannot be constructed with `new`, and instead must be
|
||||
* created by calling `initialize`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* import { Taplo } from "taplo";
|
||||
*
|
||||
* // Somewhere at the start of your app.
|
||||
* const taplo = await Taplo.initialize();
|
||||
* // ...
|
||||
* // The other methods will not return promises.
|
||||
* const formatted = taplo.format(tomlDocument);
|
||||
* ```
|
||||
*/
|
||||
export class Taplo {
|
||||
private static taplo: any | undefined;
|
||||
private static initializing: boolean = false;
|
||||
|
||||
private constructor(private env: Environment) {
|
||||
if (!Taplo.initializing) {
|
||||
throw new Error(
|
||||
`an instance of Taplo can only be created by calling the "initialize" static method`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async initialize(env?: Environment): Promise<Taplo> {
|
||||
if (typeof Taplo.taplo === "undefined") {
|
||||
Taplo.taplo = await loadTaplo();
|
||||
}
|
||||
Taplo.taplo.initialize();
|
||||
|
||||
const environment = env ?? browserEnvironment();
|
||||
prepareEnv(environment);
|
||||
|
||||
Taplo.initializing = true;
|
||||
const t = new Taplo(environment);
|
||||
Taplo.initializing = false;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint a TOML document, this function returns
|
||||
* both syntax and semantic (e.g. conflicting keys) errors.
|
||||
*
|
||||
* If a JSON schema is found in the config, the TOML document will be validated with it
|
||||
* only if it is syntactically valid.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* const lintResult = await taplo.lint(tomlDocument, {
|
||||
* config: { schema: { url: "https://example.com/my-schema.json" } },
|
||||
* });
|
||||
*
|
||||
* if (lintResult.errors.length > 0) {
|
||||
* throw new Error("the document is invalid");
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param toml TOML document.
|
||||
* @param options Optional additional options.
|
||||
*/
|
||||
public async lint(toml: string, options?: LintOptions): Promise<LintResult> {
|
||||
return await Taplo.taplo.lint(
|
||||
convertEnv(this.env),
|
||||
toml,
|
||||
objectCamel(options?.config ?? {})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given TOML document.
|
||||
*
|
||||
* @param toml TOML document.
|
||||
* @param options Optional format options.
|
||||
*/
|
||||
public format(toml: string, options?: FormatOptions): string {
|
||||
try {
|
||||
return Taplo.taplo.format(
|
||||
convertEnv(this.env),
|
||||
toml,
|
||||
options?.options ?? {},
|
||||
objectCamel(options?.config ?? {})
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given JavaScript object to TOML.
|
||||
*
|
||||
* @throws If the given object cannot be serialized to TOML.
|
||||
*
|
||||
* @param data JSON compatible JavaScript object or JSON string.
|
||||
*/
|
||||
public encode(data: object | string): string {
|
||||
if (typeof data !== "string") {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
return Taplo.taplo.from_json(data);
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given TOML string to a JavaScript object.
|
||||
*
|
||||
* @throws If data is not valid TOML.
|
||||
*
|
||||
* @param {string} data TOML string.
|
||||
*/
|
||||
public decode<T extends object = any>(data: string): T;
|
||||
|
||||
/**
|
||||
* Convert the given TOML string to JSON.
|
||||
*
|
||||
* @throws If data is not valid TOML.
|
||||
*
|
||||
* @param data TOML string.
|
||||
* @param {boolean} parse Whether to keep the JSON in a string format.
|
||||
*/
|
||||
public decode(data: string, parse: false): string;
|
||||
|
||||
public decode<T extends object = any>(
|
||||
data: string,
|
||||
parse: boolean = true
|
||||
): T | string {
|
||||
let v: string;
|
||||
try {
|
||||
v = Taplo.taplo.to_json(data);
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
if (parse) {
|
||||
return JSON.parse(v);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A very limited default environment inside a browser.
|
||||
*/
|
||||
function browserEnvironment(): Environment {
|
||||
return {
|
||||
cwd: () => "",
|
||||
envVar: () => "",
|
||||
findConfigFile: () => undefined,
|
||||
glob: () => [],
|
||||
isAbsolute: () => true,
|
||||
now: () => new Date(),
|
||||
readFile: () => Promise.reject("not implemented"),
|
||||
writeFile: () => Promise.reject("not implemented"),
|
||||
stderr: async bytes => {
|
||||
console.error(new TextDecoder().decode(bytes));
|
||||
return bytes.length;
|
||||
},
|
||||
stdErrAtty: () => false,
|
||||
stdin: () => Promise.reject("not implemented"),
|
||||
stdout: async bytes => {
|
||||
console.log(new TextDecoder().decode(bytes));
|
||||
return bytes.length;
|
||||
},
|
||||
urlToFilePath: (url: string) => url.slice("file://".length),
|
||||
};
|
||||
}
|
4
js/lib/src/rust.d.ts
vendored
Normal file
4
js/lib/src/rust.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module "*/Cargo.toml" {
|
||||
const mod: any;
|
||||
export default mod;
|
||||
}
|
34
js/lib/src/util.ts
Normal file
34
js/lib/src/util.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
const toCamel = (str: string): string => {
|
||||
return str.replace(/([_-][a-z])/gi, ($1: string) => {
|
||||
return $1.toUpperCase().replace("-", "").replace("_", "");
|
||||
});
|
||||
};
|
||||
|
||||
const isArray = function (
|
||||
input: Record<string, unknown> | Record<string, unknown>[] | unknown
|
||||
): input is Record<string, unknown>[] {
|
||||
return Array.isArray(input);
|
||||
};
|
||||
|
||||
const isObject = function (
|
||||
obj: Record<string, unknown> | Record<string, unknown>[] | unknown
|
||||
): obj is Record<string, unknown> {
|
||||
return (
|
||||
obj === Object(obj) && !Array.isArray(obj) && typeof obj !== "function"
|
||||
);
|
||||
};
|
||||
|
||||
export const objectCamel = function <T>(input: T): T {
|
||||
return (function recurse<
|
||||
K extends Record<string, unknown> | Record<string, unknown>[] | unknown
|
||||
>(input: K): K {
|
||||
if (isObject(input)) {
|
||||
return Object.keys(input).reduce((acc, key) => {
|
||||
return Object.assign(acc, { [toCamel(key)]: recurse(input[key]) });
|
||||
}, {} as K);
|
||||
} else if (isArray(input)) {
|
||||
return input.map(i => recurse(i)) as K;
|
||||
}
|
||||
return input;
|
||||
})(input);
|
||||
};
|
14
js/lib/tsconfig.json
Normal file
14
js/lib/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"lib": ["ES2019", "dom"],
|
||||
"sourceMap": false,
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"strict": false
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
6
js/lsp/.gitignore
vendored
Normal file
6
js/lsp/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
dist
|
||||
node_modules
|
||||
target
|
||||
**/.local*
|
||||
*.tgz
|
||||
yarn-error.log
|
8
js/lsp/.npmignore
Normal file
8
js/lsp/.npmignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
src
|
||||
target
|
||||
util
|
||||
**/.local*
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
rollup.config.js
|
||||
yarn-error.log
|
3
js/lsp/README.md
Normal file
3
js/lsp/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
This is a JavaScript wrapper for the Taplo TOML CLI tool.
|
||||
|
||||
All the documentation and information is available on the [website](https://taplo.tamasfe.dev).
|
32
js/lsp/package.json
Normal file
32
js/lsp/package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@taplo/lsp",
|
||||
"version": "0.3.0-alpha.4",
|
||||
"description": "A JavaScript wrapper for the Taplo TOML language server.",
|
||||
"scripts": {
|
||||
"build": "yarn rollup --silent -c rollup.config.js",
|
||||
"prepublish": "RELEASE=true yarn build"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"homepage": "https://taplo.tamasfe.dev",
|
||||
"repository": "https://github.com/tamasfe/taplo",
|
||||
"author": {
|
||||
"name": "tamasfe",
|
||||
"url": "https://github.com/tamasfe"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@types/node": "^17.0.41",
|
||||
"@wasm-tool/rollup-plugin-rust": "^2.2.2",
|
||||
"rollup": "^2.75.6",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.32.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@taplo/core": "^0.1.0"
|
||||
}
|
||||
}
|
35
js/lsp/rollup.config.js
Normal file
35
js/lsp/rollup.config.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import typescript from "rollup-plugin-typescript2";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import rust from "@wasm-tool/rollup-plugin-rust";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
export default {
|
||||
input: {
|
||||
index: "src/index.ts",
|
||||
},
|
||||
output: {
|
||||
sourcemap: false,
|
||||
name: "taploLsp",
|
||||
format: "umd",
|
||||
dir: "dist",
|
||||
},
|
||||
plugins: [
|
||||
rust({
|
||||
debug: process.env["RELEASE"] !== "true",
|
||||
nodejs: true,
|
||||
inlineWasm: true,
|
||||
cargoArgs: ["--features", "lsp"],
|
||||
}),
|
||||
commonjs(),
|
||||
resolve({
|
||||
jsnext: true,
|
||||
preferBuiltins: true,
|
||||
rootDir: path.join(process.cwd(), ".."),
|
||||
}),
|
||||
typescript(),
|
||||
terser(),
|
||||
],
|
||||
};
|
62
js/lsp/src/index.ts
Normal file
62
js/lsp/src/index.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import loadTaplo from "../../../crates/taplo-wasm/Cargo.toml";
|
||||
import { convertEnv, Environment, Lsp, prepareEnv } from "@taplo/core";
|
||||
|
||||
export interface RpcMessage {
|
||||
jsonrpc: "2.0";
|
||||
method?: string;
|
||||
id?: string | number;
|
||||
params?: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export interface LspInterface {
|
||||
/**
|
||||
* Handler for RPC messages set from the LSP server.
|
||||
*/
|
||||
onMessage: (message: RpcMessage) => void;
|
||||
}
|
||||
|
||||
export class TaploLsp {
|
||||
private static taplo: any | undefined;
|
||||
private static initializing: boolean = false;
|
||||
|
||||
private constructor(private env: Environment, private lspInner: any) {
|
||||
if (!TaploLsp.initializing) {
|
||||
throw new Error(
|
||||
`an instance of Taplo can only be created by calling the "initialize" static method`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async initialize(
|
||||
env: Environment,
|
||||
lspInterface: LspInterface
|
||||
): Promise<TaploLsp> {
|
||||
if (typeof TaploLsp.taplo === "undefined") {
|
||||
TaploLsp.taplo = await loadTaplo();
|
||||
}
|
||||
TaploLsp.taplo.initialize();
|
||||
|
||||
prepareEnv(env);
|
||||
|
||||
TaploLsp.initializing = true;
|
||||
const t = new TaploLsp(
|
||||
env,
|
||||
TaploLsp.taplo.create_lsp(convertEnv(env), {
|
||||
js_on_message: lspInterface.onMessage,
|
||||
})
|
||||
);
|
||||
TaploLsp.initializing = false;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public send(message: RpcMessage) {
|
||||
this.lspInner.send(message);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.lspInner.free();
|
||||
}
|
||||
}
|
4
js/lsp/src/rust.d.ts
vendored
Normal file
4
js/lsp/src/rust.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module "*/Cargo.toml" {
|
||||
const mod: any;
|
||||
export default mod;
|
||||
}
|
14
js/lsp/tsconfig.json
Normal file
14
js/lsp/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"lib": ["ES2019"],
|
||||
"sourceMap": false,
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
6
js/lsp/util/add_shebang.js
Normal file
6
js/lsp/util/add_shebang.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
var fs = require('fs');
|
||||
var path = "dist/cli.js";
|
||||
var data = "#!/usr/bin/env node\n\n";
|
||||
data += fs.readFileSync(path);
|
||||
fs.writeFileSync(path, data);
|
9
js/package.json
Normal file
9
js/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"cli",
|
||||
"core",
|
||||
"lib",
|
||||
"lsp"
|
||||
]
|
||||
}
|
2032
js/yarn.lock
Normal file
2032
js/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue