Skip to main content

shuck_server/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(not(test), warn(clippy::unwrap_used))]
3
4//! Language Server Protocol support for Shuck.
5//!
6//! The primary entrypoint is [`run`], which starts the server over standard
7//! input and output. A small set of document/session types is also exposed for
8//! integration tests and embedding scenarios that need an in-memory LSP server.
9
10use std::num::NonZeroUsize;
11
12pub use edit::{DocumentKey, PositionEncoding, TextDocument};
13pub use lint::generate_diagnostics;
14use lsp_types::CodeActionKind;
15pub use server::Server;
16pub use session::{Client, ClientOptions, DocumentQuery, DocumentSnapshot, GlobalOptions, Session};
17pub use workspace::{Workspace, Workspaces};
18
19mod analysis;
20mod call_hierarchy;
21mod edit;
22mod editor;
23mod editor_features;
24mod fix;
25mod folding;
26mod format;
27#[cfg(feature = "fuzzing")]
28#[doc(hidden)]
29pub mod fuzzing;
30mod lint;
31mod logging;
32mod resolve;
33mod selection;
34mod server;
35mod session;
36mod symbols;
37mod workspace;
38mod workspace_diagnostics;
39mod workspace_functions;
40
41pub(crate) const SERVER_NAME: &str = "shuck";
42pub(crate) const DIAGNOSTIC_NAME: &str = "shuck";
43
44pub(crate) const SOURCE_FIX_ALL_SHUCK: CodeActionKind = CodeActionKind::new("source.fixAll.shuck");
45
46pub(crate) type Result<T> = anyhow::Result<T>;
47
48pub(crate) fn version() -> &'static str {
49    env!("CARGO_PKG_VERSION")
50}
51
52/// Run the Shuck language server over standard input and output.
53pub fn run() -> Result<()> {
54    let four = NonZeroUsize::try_from(4usize)
55        .map_err(|_| anyhow::anyhow!("failed to create non-zero worker count"))?;
56    let worker_threads = std::thread::available_parallelism()
57        .unwrap_or(four)
58        .min(four);
59
60    let (connection, io_threads) = server::ConnectionInitializer::stdio();
61    let server_result = match start_server(worker_threads, connection)? {
62        Some(server) => server.run(),
63        None => Ok(()),
64    };
65
66    let io_result = io_threads.join();
67    match (server_result, io_result) {
68        (Ok(()), Ok(())) => Ok(()),
69        (Err(server), Ok(())) => Err(server),
70        (Ok(()), Err(io)) => Err(anyhow::Error::new(io).context("IO thread error")),
71        (Err(server), Err(io)) => Err(server.context(format!("IO thread error: {io}"))),
72    }
73}
74
75#[doc(hidden)]
76pub fn run_connection(connection: lsp_server::Connection) -> Result<()> {
77    let four = NonZeroUsize::try_from(4usize)
78        .map_err(|_| anyhow::anyhow!("failed to create non-zero worker count"))?;
79    let worker_threads = std::thread::available_parallelism()
80        .unwrap_or(four)
81        .min(four);
82    match start_server(
83        worker_threads,
84        server::ConnectionInitializer::from_connection(connection),
85    )? {
86        Some(server) => server.run(),
87        None => Ok(()),
88    }
89}
90
91fn start_server(
92    worker_threads: NonZeroUsize,
93    connection: server::ConnectionInitializer,
94) -> Result<Option<Server>> {
95    match Server::new(worker_threads, connection) {
96        Ok(server) => Ok(Some(server)),
97        Err(error) if is_disconnected(&error) => Ok(None),
98        Err(error) => Err(error.context("Failed to start server")),
99    }
100}
101
102fn is_disconnected(error: &anyhow::Error) -> bool {
103    error
104        .downcast_ref::<lsp_server::ProtocolError>()
105        .is_some_and(lsp_server::ProtocolError::channel_is_disconnected)
106}