diff --git a/Justfile b/Justfile new file mode 100644 index 000000000..540107c28 --- /dev/null +++ b/Justfile @@ -0,0 +1,8 @@ +build: + cd crates/taplo-cli && cargo build -F lsp -Fcargo_toml + +run: + cd crates/taplo-cli && cargo run -F lsp -Fcargo_toml lsp tcp + +install: + cd crates/taplo-cli && cargo install -F lsp -Fcargo_toml --path . diff --git a/crates/taplo-cli/Cargo.toml b/crates/taplo-cli/Cargo.toml index 19d84810f..b05eeb347 100644 --- a/crates/taplo-cli/Cargo.toml +++ b/crates/taplo-cli/Cargo.toml @@ -17,6 +17,7 @@ lsp = ["async-ctrlc", "taplo-lsp"] native-tls = ["taplo-common/native-tls", "taplo-lsp?/native-tls"] rustls-tls = ["taplo-common/rustls-tls", "taplo-lsp?/rustls-tls"] toml-test = [] +cargo_toml = ["taplo-lsp?/cargo_toml"] [dependencies] taplo = { path = "../taplo", features = ["serde"] } diff --git a/crates/taplo-lsp/Cargo.toml b/crates/taplo-lsp/Cargo.toml index 6e5dbbdaa..97c105b64 100644 --- a/crates/taplo-lsp/Cargo.toml +++ b/crates/taplo-lsp/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["cdylib", "rlib"] default = ["rustls-tls"] native-tls = ["taplo-common/native-tls"] rustls-tls = ["taplo-common/rustls-tls"] +cargo_toml = [] [dependencies] lsp-async-stub = { path = "../lsp-async-stub" } diff --git a/crates/taplo-lsp/src/handlers.rs b/crates/taplo-lsp/src/handlers.rs index f765c75c4..2e5e25543 100644 --- a/crates/taplo-lsp/src/handlers.rs +++ b/crates/taplo-lsp/src/handlers.rs @@ -39,3 +39,9 @@ pub(crate) use rename::*; mod conversion; pub(crate) use conversion::*; + +mod code_action; +pub(crate) use code_action::*; + +#[cfg(feature = "cargo_toml")] +pub(crate) mod cargo; diff --git a/crates/taplo-lsp/src/handlers/cargo.rs b/crates/taplo-lsp/src/handlers/cargo.rs new file mode 100644 index 000000000..e29cb9686 --- /dev/null +++ b/crates/taplo-lsp/src/handlers/cargo.rs @@ -0,0 +1,5 @@ +mod completion; +pub(crate) use completion::*; + +mod code_action; +pub(crate) use code_action::*; diff --git a/crates/taplo-lsp/src/handlers/cargo/code_action.rs b/crates/taplo-lsp/src/handlers/cargo/code_action.rs new file mode 100644 index 000000000..fea5bf886 --- /dev/null +++ b/crates/taplo-lsp/src/handlers/cargo/code_action.rs @@ -0,0 +1,70 @@ +#![allow(unused)] +use std::{collections::HashMap, path::Path}; + +use lsp_async_stub::{rpc::Error, Context, Params}; +use lsp_types::{ + CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, + Position, Range, TextEdit, WorkspaceEdit, +}; +use taplo::dom::{Keys, Node}; +use taplo_common::environment::Environment; + +use crate::world::{DocumentState, World}; + +pub async fn code_action( + params: CodeActionParams, + path: Keys, + node: Node, + doc: &DocumentState, +) -> Result, Error> { + let document_uri = ¶ms.text_document.uri; + let position = params.range.start; + + let mut dotted = path.dotted().split(".").peekable(); + let location = dotted.next().unwrap_or_default(); + if !["dependencies", "dev-dependencies", "build-dependencies"].contains(&location) { + return Ok(None); + } + let package = dotted.next().unwrap_or_default(); + + dbg!(&node); + dbg!(path.dotted()); + let mut actions = Vec::new(); + + if dotted.peek().is_none() { + match &node { + Node::Str(s) => { + let version = s.value(); + let range = node + .text_ranges(true) + .next() + .and_then(|r| doc.mapper.range(r)); + let Some(range) = range else { + return Ok(None); + }; + let start = Position::new(range.start.line as u32, range.start.character as u32); + let end = Position::new(range.end.line as u32, range.end.character as u32); + let range = Range::new(start, end); + let edit = TextEdit::new(range, format!("{{ version = \"{version}\" }}")); + let mut map = HashMap::new(); + map.insert(document_uri.clone(), vec![edit]); + let action = CodeAction { + title: "Expand dependency specification".to_string(), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(map), + ..WorkspaceEdit::default() + }), + ..CodeAction::default() + }; + actions.push(CodeActionOrCommand::CodeAction(action)); + } + _ => return Ok(None), + } + } + if actions.is_empty() { + Ok(None) + } else { + Ok(Some(actions)) + } +} diff --git a/crates/taplo-lsp/src/handlers/cargo/completion.rs b/crates/taplo-lsp/src/handlers/cargo/completion.rs new file mode 100644 index 000000000..b731c4a60 --- /dev/null +++ b/crates/taplo-lsp/src/handlers/cargo/completion.rs @@ -0,0 +1,179 @@ +use std::{ + path::{Path, PathBuf}, + sync::LazyLock, +}; + +use lsp_async_stub::rpc::Error; +use lsp_types::{ + CompletionItem, CompletionItemKind, CompletionList, CompletionParams, CompletionResponse, + InsertTextFormat, +}; +use taplo::dom::{node::TableKind, Keys, Node}; + +use crate::query::Query; + +// we want completions for + +static TOP_1000_RECENTLY_DOWNLOADED_CRATES: &str = include_str!("./top_1000_recent_crates.txt"); + +pub struct IndexCache { + pub static_crate_rank: Vec>, +} + +#[derive(Debug, Clone)] +pub struct PackageRef<'a> { + pub name: &'a str, +} + +impl IndexCache { + pub fn new() -> Self { + let static_crate_rank = TOP_1000_RECENTLY_DOWNLOADED_CRATES + .trim_end_matches('\n') + .split('\n') + .map(|n| PackageRef { name: n }) + .collect(); + Self { static_crate_rank } + } + + pub fn completion_for_package(&self, query: &str) -> Vec> { + self.static_crate_rank + .iter() + .filter(|c| c.name.starts_with(query)) + .cloned() + .collect() + } + + pub fn versions_for_package(&self, _package: &str) -> Vec<&str> { + vec!["1.0", "2.0"] + } + + pub fn features_for_package(&self, _package: &str) -> Vec<&str> { + vec!["uuid", "postgres", "serde"] + } +} + +static INDEX_CACHE: LazyLock = LazyLock::new(|| IndexCache::new()); + +fn crate_index_path(package: &str) -> PathBuf { + let l = package.len(); + match l { + 1 => Path::new(&package).to_owned(), + 2 => Path::new("2").join(package).to_owned(), + 3 => Path::new("3").join(&package[..1]).join(package).to_owned(), + _ => Path::new(&package[..2]) + .join(&package[2..4]) + .join(package) + .to_owned(), + } +} + +pub fn complete_dependencies( + _params: CompletionParams, + query: Query, + path: Keys, + node: Node, +) -> Result, Error> { + let mut dotted = path.dotted().split(".").skip(1).peekable(); + let package = dotted.next().unwrap_or_default(); + + if dotted.peek().is_none() + && matches!(&node, Node::Table(t) if matches!(t.kind(), TableKind::Regular)) + { + // package is in header, e.g. [dependencies.tokio] + let items = INDEX_CACHE + .completion_for_package(package) + .into_iter() + .take(1) + .map(|p| { + let name = p.name.to_string(); + let completion = CompletionItem::new_simple(name.clone(), name.clone()); + // completion.insert_text = Some(format!("{name}$0")); + // completion.insert_text_format = Some(InsertTextFormat::SNIPPET); + // completion.kind = Some(CompletionItemKind::MODULE); + completion + }) + .collect(); + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items, + }))); + } + + if dotted.peek().is_none() && matches!(&node, Node::Invalid(_)) { + // package is in a table. e.g. + // [dependencies] + // tokio = "1.0" + let items = INDEX_CACHE + .completion_for_package(package) + .into_iter() + .take(1) + .map(|p| { + let name = p.name.to_string(); + let mut completion = CompletionItem::new_simple(name.clone(), name.clone()); + completion.insert_text = Some(format!("{name} = \"$1\"$0")); + completion.insert_text_format = Some(InsertTextFormat::SNIPPET); + completion.kind = Some(CompletionItemKind::MODULE); + completion + }) + .collect(); + return Ok(Some(CompletionResponse::List(CompletionList { + is_incomplete: true, + items, + }))); + } + + let next = dotted.next().unwrap_or("version"); + if query.in_inline_table() || !["version", "features", "optional"].contains(&next) { + // we are in an inline table, or we are not in a known key + let mut items = Vec::with_capacity(3); + let k = "version"; + let mut item = CompletionItem::new_simple(k.into(), k.into()); + item.insert_text = Some(format!("{k} = \"$1\"$0")); + item.insert_text_format = Some(InsertTextFormat::SNIPPET); + items.push(item); + let k = "features"; + let mut item = CompletionItem::new_simple(k.into(), k.into()); + item.insert_text = Some(format!("{k} = [\"$1\"]$0")); + item.insert_text_format = Some(InsertTextFormat::SNIPPET); + items.push(item); + let k = "optional"; + let mut item = CompletionItem::new_simple(k.into(), k.into()); + item.insert_text = Some(format!("{k} = true$0")); + item.insert_text_format = Some(InsertTextFormat::SNIPPET); + items.push(item); + return Ok(Some(CompletionResponse::Array(items))); + } + + match next { + "version" => { + dbg!("version"); + let versions = INDEX_CACHE.versions_for_package(package); + let completions = versions + .into_iter() + .map(|v| CompletionItem::new_simple(v.to_string(), v.to_string())) + .collect(); + Ok(Some(CompletionResponse::Array(completions))) + } + "features" => { + dbg!("features"); + let features = INDEX_CACHE.features_for_package(package); + let completions = features + .into_iter() + .map(|f| { + let completion = CompletionItem::new_simple(f.to_string(), f.to_string()); + completion + }) + .collect(); + Ok(Some(CompletionResponse::Array(completions))) + } + "optional" => { + dbg!("optional"); + let completions = vec![ + CompletionItem::new_simple("true".into(), "true".into()), + CompletionItem::new_simple("false".into(), "false".into()), + ]; + Ok(Some(CompletionResponse::Array(completions))) + } + _ => Ok(None), + } +} diff --git a/crates/taplo-lsp/src/handlers/cargo/top_1000_recent_crates.txt b/crates/taplo-lsp/src/handlers/cargo/top_1000_recent_crates.txt new file mode 100644 index 000000000..719615fec --- /dev/null +++ b/crates/taplo-lsp/src/handlers/cargo/top_1000_recent_crates.txt @@ -0,0 +1,1000 @@ +syn +bitflags +hashbrown +base64 +regex-syntax +indexmap +regex-automata +proc-macro2 +itertools +quote +libc +heck +unicode-ident +serde +memchr +serde_derive +autocfg +cfg-if +aho-corasick +once_cell +serde_json +rand_core +regex +itoa +getrandom +rand +strsim +ryu +clap +cc +windows_x86_64_msvc +smallvec +parking_lot_core +windows-sys +thiserror-impl +thiserror +log +rustix +parking_lot +windows-targets +windows_x86_64_gnu +either +lazy_static +windows_i686_msvc +windows_aarch64_msvc +windows_i686_gnu +windows_x86_64_gnullvm +windows_aarch64_gnullvm +rand_chacha +linux-raw-sys +lock_api +idna +http +scopeguard +socket2 +rustls +fastrand +semver +num-traits +toml_edit +anyhow +bytes +version_check +ahash +hyper +clap_lex +digest +miniz_oxide +mio +tinyvec +memoffset +http-body +block-buffer +unicode-normalization +pin-project-lite +toml +ppv-lite86 +crossbeam-utils +tokio +tinyvec_macros +rustls-pemfile +byteorder +errno +winnow +spin +url +tokio-rustls +sha2 +equivalent +percent-encoding +zerocopy +futures-core +rustls-webpki +h2 +futures-util +futures-task +slab +futures-sink +unicode-bidi +futures-channel +redox_syscall +nom +cpufeatures +tracing +tracing-core +generic-array +clap_derive +uuid +rustc_version +time +clap_builder +pin-utils +sync_wrapper +typenum +anstyle +chrono +form_urlencoded +futures-io +fnv +tempfile +ring +untrusted +pkg-config +tokio-util +futures +minimal-lexical +strum_macros +tokio-macros +anstream +futures-macro +strum +crypto-common +anstyle-parse +nix +anstyle-query +futures-executor +env_logger +event-listener +darling_core +darling_macro +darling +iana-time-zone +colorchoice +httparse +thread_local +subtle +flate2 +hyper-rustls +utf8parse +crc32fast +num-integer +reqwest +crossbeam-epoch +rustc-hash +toml_datetime +object +async-trait +tracing-attributes +rustversion +signal-hook-registry +hex +zeroize +libloading +gimli +glob +mime +num_cpus +time-macros +crossbeam-deque +webpki-roots +tower-service +paste +unicode-width +shlex +try-lock +zerocopy-derive +num-bigint +hermit-abi +want +time-core +httpdate +prost +prost-derive +termcolor +tracing-log +adler +hmac +proc-macro-crate +pin-project +pin-project-internal +addr2line +deranged +walkdir +rustc-demangle +rayon +encoding_rs +ipnet +wasi +rayon-core +serde_urlencoded +powerfmt +backtrace +humantime +tracing-subscriber +winapi +bumpalo +same-file +arrayvec +crossbeam-channel +jobserver +vcpkg +num-conv +rustls-native-certs +prettyplease +wasm-bindgen +wasm-bindgen-backend +wasm-bindgen-macro +wasm-bindgen-macro-support +wasm-bindgen-shared +openssl-probe +static_assertions +which +phf_shared +convert_case +is_terminal_polyfill +nu-ansi-term +sharded-slab +siphasher +openssl-sys +tower +sha1 +winapi-x86_64-pc-windows-gnu +winapi-i686-pc-windows-gnu +allocator-api2 +ident_case +unicode-segmentation +ordered-float +tower-layer +home +serde_spanned +unicode-xid +textwrap +foreign-types-shared +foreign-types +windows_i686_gnullvm +half +memmap2 +proc-macro-error +proc-macro-error-attr +openssl +overload +target-lexicon +libm +serde_with +is-terminal +bindgen +js-sys +rustls-pki-types +serde_with_macros +prost-types +matchers +phf +axum +petgraph +tokio-stream +zstd-safe +axum-core +phf_generator +bstr +fixedbitset +bit-vec +signature +hyper-tls +zstd +indoc +openssl-macros +crunchy +instant +bytemuck +async-lock +bit-set +hyper-util +futures-lite +der +http-body-util +lru +core-foundation-sys +tonic +async-channel +native-tls +winapi-util +sct +data-encoding +dashmap +portable-atomic +spki +prost-build +num-iter +web-sys +num-rational +atty +zstd-sys +atomic-waker +pbkdf2 +matchit +constant_time_eq +cfg_aliases +num-complex +derive_more +tokio-native-tls +cipher +clang-sys +pkcs8 +opentelemetry +filetime +csv +csv-core +num_enum +num_enum_derive +pyo3-build-config +unindent +const-oid +lazycell +unicase +serde_yaml +opaque-debug +polling +pyo3 +pyo3-macros +pyo3-macros-backend +windows-core +cexpr +bincode +pest +pyo3-ffi +md-5 +tungstenite +hyper-timeout +concurrent-queue +base64ct +async-stream +async-stream-impl +quick-xml +aes +multimap +wasm-bindgen-futures +event-listener-strategy +async-io +anstyle-wincon +pem +parking +android_system_properties +ucd-trie +phf_codegen +linked-hash-map +tokio-tungstenite +derivative +winreg +crossbeam-queue +pest_derive +pest_meta +iana-time-zone-haiku +arc-swap +pest_generator +zeroize_derive +urlencoding +core-foundation +io-lifetimes +num +android-tzdata +windows +quick-error +criterion +yansi +tower-http +arrayref +stable_deref_trait +combine +unsafe-libyaml +utf-8 +valuable +libz-sys +tap +dyn-clone +camino +serde_bytes +mime_guess +tiny-keccak +inout +security-framework-sys +tonic-build +crypto-bigint +security-framework +console +predicates +brotli-decompressor +bitvec +fancy-regex +radium +wyz +dirs-sys +borsh +hostname +schannel +funty +flume +borsh-derive +brotli +xattr +displaydoc +oorandom +ciborium-io +ciborium-ll +ciborium +cast +tracing-opentelemetry +hashlink +hkdf +os_str_bytes +pem-rfc7468 +tinytemplate +criterion-plot +alloc-no-stdlib +num-derive +alloc-stdlib +synstructure +cargo_metadata +twox-hash +serde_path_to_error +fallible-iterator +diff +num_threads +tar +tracing-serde +opentelemetry_sdk +peeking_take_while +plotters +globset +plotters-backend +curve25519-dalek +plotters-svg +predicates-core +tokio-io-timeout +predicates-tree +cargo-platform +anes +dirs +pretty_assertions +futures-timer +waker-fn +mockall +mockall_derive +termtree +cbindgen +doc-comment +async-compression +cookie +base16ct +raw-cpuid +crc +async-task +zip +match_cfg +ansi_term +ff +group +signal-hook +float-cmp +simd-adler32 +erased-serde +scoped-tls +libsqlite3-sys +adler2 +elliptic-curve +blake2 +sec1 +quanta +ecdsa +bzip2-sys +blocking +cmake +rsa +semver-parser +dirs-sys-next +quinn-proto +const-random +serde_repr +sha3 +atoi +quinn-udp +const-random-macro +headers +phf_macros +quinn +rfc6979 +ed25519 +pkcs1 +headers-core +blake3 +terminal_size +crc-catalog +downcast-rs +system-configuration +regex-lite +system-configuration-sys +num-bigint-dig +encode_unicode +serde_derive_internals +rand_xorshift +fragile +jsonwebtoken +chrono-tz +difflib +whoami +downcast +chrono-tz-build +crypto-mac +stringprep +wait-timeout +keccak +tracing-futures +dirs-next +aead +similar +matches +piper +serial_test +enum-as-inner +ed25519-dalek +vec_map +dunce +serial_test_derive +simple_asn1 +block-padding +ctr +maplit +universal-hash +fs_extra +image +lexical-core +colored +pathdiff +bytes-utils +value-bag +bzip2 +jni +debugid +xml-rs +new_debug_unreachable +option-ext +opentelemetry-semantic-conventions +async-executor +schemars +schemars_derive +rand_distr +env_filter +outref +Inflector +http-range-header +base64-simd +lz4_flex +fxhash +async-recursion +opentelemetry-http +rmp +owo-colors +redox_users +png +webpki +backoff +parse-zoneinfo +snafu-derive +snafu +sha-1 +ascii +polyval +aws-smithy-types +ureq +sha1_smol +aws-smithy-http +triomphe +async-process +aws-sigv4 +xmlparser +arbitrary +lexical-util +lexical-parse-float +lexical-parse-integer +bs58 +aws-types +wasm-streams +md5 +secrecy +bytemuck_derive +aws-smithy-async +aws-sdk-sts +dotenvy +yaml-rust +aws-smithy-xml +aws-smithy-json +simdutf8 +p256 +lexical-write-integer +lexical-write-float +aws-config +indicatif +object_store +rand_hc +void +aws-sdk-sso +unicode_categories +aws-smithy-query +ignore +opentelemetry-otlp +lru-cache +opentelemetry-proto +proptest +bytecount +ctrlc +derive_builder +derive_builder_core +number_prefix +sysinfo +rmp-serde +threadpool +infer +fdeflate +seahash +normalize-line-endings +syn_derive +precomputed-hash +sqlformat +string_cache +gif +ghash +kv-log-macro +aes-gcm +libredox +multer +comfy-table +crossbeam +castaway +protobuf +derive_builder_macro +const_format +const_format_proc_macros +rand_pcg +handlebars +unarray +hdrhistogram +futures-intrusive +codespan-reporting +assert-json-diff +resolv-conf +fiat-crypto +ark-std +sqlx-core +serde-value +ark-serialize +ark-ff-macros +ark-ff-asm +sqlx +crossterm +git2 +indenter +sqlx-macros +proc-macro-hack +rusty-fork +ark-ff +der-parser +libgit2-sys +asn1-rs +jni-sys +approx +flatbuffers +widestring +snap +asn1-rs-derive +ouroboros +ouroboros_macro +curve25519-dalek-derive +oid-registry +rstest +integer-encoding +x509-parser +asn1-rs-impl +term +pulldown-cmark +borsh-derive-internal +borsh-schema-derive-internal +crc32c +ptr_meta +ptr_meta_derive +cesu8 +color_quant +shell-words +unicode-properties +rust-ini +trust-dns-proto +aws-credential-types +rkyv +rkyv_derive +derive_arbitrary +eyre +weezl +hash32 +ctor +bytecheck_derive +bytecheck +structopt +wasmparser +assert_matches +yada +structopt-derive +rend +lz4-sys +rstest_macros +vsimd +relative-path +actix-rt +matrixmultiply +windows-result +sqlx-sqlite +sqlx-macros-core +config +arrow-buffer +arrow-schema +arrow-array +arrow-data +web-time +remove_dir_all +serde_qs +sqlx-postgres +thrift +sqlx-mysql +aws-smithy-runtime +arrow-select +arrow-cast +aws-runtime +rand_xoshiro +inotify +dlv-list +async-global-executor +jsonpath-rust +aws-smithy-runtime-api +openssl-src +rust_decimal +clap_complete +jpeg-decoder +arrow-ipc +rusticata-macros +language-tags +actix-web +ordered-multimap +actix-macros +prometheus +actix-http +unicode-linebreak +tikv-jemalloc-sys +tonic-reflection +gethostname +os_pipe +sqlparser +getopts +actix-server +rpassword +metrics +smawk +rawpointer +bytestring +async-broadcast +aws-sdk-s3 +axum-macros +actix-codec +actix-router +deadpool +dtoa +adler32 +json-patch +aws-smithy-eventstream +actix-web-codegen +inotify-sys +actix-utils +actix-service +ron +async-std +aliasable +trust-dns-resolver +aws-smithy-checksums +rgb +psm +fallible-streaming-iterator +arrow +arrow-arith +nanorand +arrow-ord +arrow-string +bitmaps +core_affinity +local-channel +compact_str +local-waker +tikv-jemallocator +arrow-row +opentelemetry_api +cfg-expr +aws-sdk-ssooidc +assert_cmd +tiff +seq-macro +tonic-health +tagptr +nohash-hasher +primeorder +no-std-compat +fs-err +deadpool-runtime +wasm-encoder +password-hash +ipnetwork +sized-chunks +simple_logger +arrow-csv +lz4 +arrow-json +governor +unsigned-varint +nonzero_ext +moka +notify +fraction +rtoolbox +libsecp256k1 +if_chain +wasite +ark-serialize-derive +ark-poly +k256 +async-signal +test-log +rocksdb +librocksdb-sys +ark-ec +serde-wasm-bindgen +atomic +libsecp256k1-core +libsecp256k1-gen-genmult +libsecp256k1-gen-ecmult +enumflags2 +enumflags2_derive +smol_str +typed-builder +cpp_demangle +psl-types +signal-hook-mio +bytesize +quickcheck +bigdecimal +hmac-drbg +cxxbridge-macro +cxx +sqlparser_derive +procfs +leb128 +cxxbridge-flags +rusqlite +humantime-serde +redis +gloo-timers +link-cplusplus +poly1305 +miette +miette-derive +json5 +chacha20 +tokio-test +tracing-appender +metrics-util +send_wrapper +async-fs +ntapi +multihash +raw-window-handle +cadence +heapless +reqwest-middleware +http-types +validator +unreachable +console_error_panic_hook +cxx-build +lockfree-object-pool +roaring +im +hex-literal +ipconfig +lalrpop-util +iso8601 +warp +zopfli +color-eyre +enum-ordinalize +ena +jsonschema +chunked_transfer +windows-implement +scratch +educe +directories +uncased +finl_unicode +sketches-ddsketch +pretty_env_logger +chacha20poly1305 +validator_derive +dialoguer +quick-protobuf +string_cache_codegen +k8s-openapi +ttf-parser +etcetera +x25519-dalek +num-cmp +ref-cast-impl +ref-cast +parity-scale-codec +include_dir +blake2b_simd +visibility +secp256k1 +kube +windows-strings +slotmap +stacker +windows-interface +datafusion +kube-core +kube-client +datafusion-common +datafusion-expr +datafusion-physical-expr +datafusion-optimizer +mach +datafusion-sql +datafusion-execution +parity-scale-codec-derive +datafusion-physical-plan +uint +tiny_http +secp256k1-sys +wide +nalgebra +datafusion-functions +datafusion-common-runtime +serde_fmt +ct-codecs +metrics-macros +value-bag-serde1 +yasna +tempdir +lalrpop +datafusion-physical-expr-common +datafusion-functions-aggregate +sval +deunicode +primitive-types +byte-slice-cast +globwalk +tikv-jemalloc-ctl +tiny-bip39 +sval_json +bit_field +sval_dynamic +sval_fmt +tokio-retry +sval_buffer +sval_ref +sval_serde +markup5ever +lzma-sys +serde_cbor +value-bag-sval2 +fslock +safe_arch +xz2 +version-compare +xxhash-rust +html5ever +sentry-types +ark-bn254 +kube-runtime +parquet +fixed-hash +opentelemetry-datadog +tinystr +ndarray +opentelemetry-stdout +ascii-canvas +fuchsia-cprng +pkcs5 diff --git a/crates/taplo-lsp/src/handlers/code_action.rs b/crates/taplo-lsp/src/handlers/code_action.rs new file mode 100644 index 000000000..bd2022383 --- /dev/null +++ b/crates/taplo-lsp/src/handlers/code_action.rs @@ -0,0 +1,59 @@ +#![allow(unused)] +use lsp_async_stub::{ + rpc::Error, + util::{LspExt, Position}, + Context, Params, +}; +use lsp_types::{CodeAction, CodeActionParams, CodeActionResponse}; +use taplo::dom::{node::TableKind, Keys, Node}; +use taplo_common::environment::Environment; + +use crate::{query::Query, world::World}; + +pub async fn code_action( + context: Context>, + params: Params, +) -> Result, Error> { + let params = params.required()?; + let document_uri = ¶ms.text_document.uri; + + #[cfg(feature = "cargo_toml")] + { + if !document_uri.path().ends_with("Cargo.toml") { + return Ok(None); + } + + let workspaces = context.workspaces.read().await; + let ws = workspaces.by_document(document_uri); + + let doc = match ws.document(document_uri) { + Ok(d) => d, + Err(error) => { + tracing::debug!(%error, "failed to get document from workspace"); + return Ok(None); + } + }; + + let position = params.range.start; + let Some(offset) = doc.mapper.offset(Position::from_lsp(position)) else { + tracing::error!(?position, "document position not found"); + return Ok(None); + }; + + let query = Query::at(&doc.dom, offset); + + let (path, node) = query + .dom_node() + .cloned() + .or_else(|| { + doc.dom + .flat_iter() + .rev() + .find(|n| matches!(&n.1, Node::Table(t) if t.kind() == TableKind::Regular)) + }) + .unwrap_or_else(|| (Keys::empty(), doc.dom.clone())); + + return super::cargo::code_action(params, path, node, doc).await; + } + return Ok(None); +} diff --git a/crates/taplo-lsp/src/handlers/completion.rs b/crates/taplo-lsp/src/handlers/completion.rs index be49549dc..54b9d70f7 100644 --- a/crates/taplo-lsp/src/handlers/completion.rs +++ b/crates/taplo-lsp/src/handlers/completion.rs @@ -28,17 +28,17 @@ pub async fn completion( ) -> Result, Error> { let p = params.required()?; - let document_uri = p.text_document_position.text_document.uri; + let document_uri = &p.text_document_position.text_document.uri; let workspaces = context.workspaces.read().await; - let ws = workspaces.by_document(&document_uri); + let ws = workspaces.by_document(document_uri); // All completions are tied to schemas. if !ws.config.schema.enabled { return Ok(None); } - let doc = match ws.document(&document_uri) { + let doc = match ws.document(document_uri) { Ok(d) => d, Err(error) => { tracing::debug!(%error, "failed to get document from workspace"); @@ -46,11 +46,13 @@ pub async fn completion( } }; - let Some(schema_association) = ws.schemas.associations().association_for(&document_uri) else { + let Some(schema_association) = ws.schemas.associations().association_for(document_uri) else { return Ok(None); }; let position = p.text_document_position.position; + + // this errors out when a character gets deleted... let Some(offset) = doc.mapper.offset(Position::from_lsp(position)) else { tracing::error!(?position, "document position not found"); return Ok(None); @@ -66,6 +68,26 @@ pub async fn completion( } }; + let (path, node) = query + .dom_node() + .cloned() + .or_else(|| { + doc.dom + .flat_iter() + .rev() + .find(|n| matches!(&n.1, Node::Table(t) if t.kind() == TableKind::Regular)) + }) + .unwrap_or_else(|| (Keys::empty(), doc.dom.clone())); + + #[cfg(feature = "cargo_toml")] + if document_uri.path().ends_with("Cargo.toml") { + let dotted = path.dotted(); + let k = dotted.split('.').next().unwrap_or_default(); + if ["dependencies", "dev-dependencies", "build-dependencies"].contains(&k) { + return super::cargo::complete_dependencies(p, query, path, node); + } + } + if query.in_table_header() { let key_count = query.header_keys().len(); @@ -101,18 +123,12 @@ pub async fn completion( Some(r) } }); - - let node = query - .dom_node() - .cloned() - .unwrap_or_else(|| (Keys::empty(), doc.dom.clone())); - return Ok(Some(CompletionResponse::Array( object_schemas // Filter out existing tables in the dom. .filter(|(full_key, _, _)| match doc.dom.path(full_key) { Some(n) => { - node.0 == *full_key + path == *full_key || n.as_table() .map_or(false, |t| t.kind() == TableKind::Pseudo) } diff --git a/crates/taplo-lsp/src/handlers/initialize.rs b/crates/taplo-lsp/src/handlers/initialize.rs index 3ae4e539d..82b85f5d8 100644 --- a/crates/taplo-lsp/src/handlers/initialize.rs +++ b/crates/taplo-lsp/src/handlers/initialize.rs @@ -6,11 +6,12 @@ use crate::world::WorkspaceState; use crate::World; use lsp_async_stub::{rpc::Error, Context, Params}; use lsp_types::{ - CompletionOptions, DocumentLinkOptions, FoldingRangeProviderCapability, - HoverProviderCapability, InitializedParams, OneOf, RenameOptions, SemanticTokensFullOptions, - SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities, - ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, - WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, + CodeActionProviderCapability, CompletionOptions, DocumentLinkOptions, + FoldingRangeProviderCapability, 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; @@ -97,6 +98,7 @@ pub async fn initialize( resolve_provider: None, work_done_progress_options: Default::default(), }), + code_action_provider: Some(CodeActionProviderCapability::Simple(true)), ..Default::default() }, server_info: Some(ServerInfo { diff --git a/crates/taplo-lsp/src/lib.rs b/crates/taplo-lsp/src/lib.rs index e4a1008ea..01a2ba133 100644 --- a/crates/taplo-lsp/src/lib.rs +++ b/crates/taplo-lsp/src/lib.rs @@ -37,6 +37,7 @@ pub fn create_server() -> Server> { .on_request::(handlers::hover) .on_request::(handlers::links) .on_request::(handlers::semantic_tokens) + .on_request::(handlers::code_action) .on_request::(handlers::prepare_rename) .on_request::(handlers::rename) .on_notification::(handlers::initialized) diff --git a/script/top_crates.sh b/script/top_crates.sh new file mode 100755 index 000000000..dfebf203d --- /dev/null +++ b/script/top_crates.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# URL to fetch crates data +BASE_URL="https://crates.io/api/v1/crates" +OUTPUT_FILE="top_1000_recent_crates.txt" + +# Initialize variables +URL="$BASE_URL?per_page=100&sort=recent-downloads" + +true > "$OUTPUT_FILE" + +# hack for getting around kurt's custom jq version +JQ_ARGS="-r" +if [[ "$(which jq)" = "$HOME/.cargo/bin/jq" ]]; then + JQ_ARGS="" +fi + +# Loop to fetch and process each page +for _ in {1..10}; do + echo Requesting "$URL" + RES=$(curl -s "$URL") + NEXT_PAGE=$(echo "$RES" | jq $JQ_ARGS '.meta.next_page') + URL="$BASE_URL$NEXT_PAGE" + echo "$RES" | jq $JQ_ARGS '.crates[].name' >> "$OUTPUT_FILE" +done