Skip to main content

shuck_server/
server.rs

1use lsp_server::Connection;
2use lsp_types as types;
3use lsp_types::InitializeParams;
4use lsp_types::{
5    ClientCapabilities, CodeActionKind, CodeActionOptions, DiagnosticOptions, OneOf,
6    TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
7    WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities,
8};
9use std::num::NonZeroUsize;
10
11pub(crate) use self::connection::ConnectionInitializer;
12pub use self::connection::ConnectionSender;
13use self::schedule::spawn_main_loop;
14use crate::PositionEncoding;
15pub use crate::server::main_loop::MainLoopSender;
16pub(crate) use crate::server::main_loop::{Event, MainLoopReceiver};
17use crate::session::{AllOptions, Client, Session};
18use crate::workspace::Workspaces;
19pub(crate) use api::Error;
20
21mod api;
22mod connection;
23mod main_loop;
24mod schedule;
25
26pub(crate) type Result<T> = std::result::Result<T, api::Error>;
27
28pub struct Server {
29    connection: Connection,
30    client_capabilities: ClientCapabilities,
31    worker_threads: NonZeroUsize,
32    main_loop_receiver: MainLoopReceiver,
33    main_loop_sender: MainLoopSender,
34    session: Session,
35}
36
37impl Server {
38    pub(crate) fn new(
39        worker_threads: NonZeroUsize,
40        connection: ConnectionInitializer,
41    ) -> crate::Result<Self> {
42        let (id, init_params) = connection.initialize_start()?;
43        let client_capabilities = init_params.capabilities;
44        let position_encoding = Self::find_best_position_encoding(&client_capabilities);
45        let server_capabilities = Self::server_capabilities(position_encoding);
46        let connection = connection.initialize_finish(
47            id,
48            &server_capabilities,
49            crate::SERVER_NAME,
50            crate::version(),
51        )?;
52
53        let (main_loop_sender, main_loop_receiver) = crossbeam::channel::bounded(32);
54
55        #[allow(deprecated)]
56        let InitializeParams {
57            initialization_options,
58            root_path,
59            root_uri,
60            workspace_folders,
61            ..
62        } = init_params;
63
64        let client = Client::new(main_loop_sender.clone(), connection.sender.clone());
65        let AllOptions { global, workspace } = AllOptions::from_value(
66            initialization_options.unwrap_or(serde_json::Value::Null),
67            &client,
68        );
69
70        crate::logging::init_logging(global.tracing.log_level.unwrap_or_default(), None);
71
72        let workspaces = Workspaces::from_workspace_folders(
73            workspace_folders,
74            root_uri,
75            root_path,
76            workspace.unwrap_or_default(),
77        )?;
78        let global = global.into_settings(client.clone());
79
80        Ok(Self {
81            connection,
82            client_capabilities: client_capabilities.clone(),
83            worker_threads,
84            main_loop_receiver,
85            main_loop_sender,
86            session: Session::new(
87                &client_capabilities,
88                position_encoding,
89                global,
90                &workspaces,
91                &client,
92            )?,
93        })
94    }
95
96    pub fn run(mut self) -> crate::Result<()> {
97        spawn_main_loop(move || self.main_loop())?
98            .join()
99            .map_err(|_| anyhow::anyhow!("main loop thread panicked"))?
100    }
101
102    fn find_best_position_encoding(client_capabilities: &ClientCapabilities) -> PositionEncoding {
103        client_capabilities
104            .general
105            .as_ref()
106            .and_then(|general| general.position_encodings.as_ref())
107            .and_then(|encodings| {
108                encodings
109                    .iter()
110                    .filter_map(|encoding| PositionEncoding::try_from(encoding).ok())
111                    .max()
112            })
113            .unwrap_or_default()
114    }
115
116    fn server_capabilities(position_encoding: PositionEncoding) -> types::ServerCapabilities {
117        types::ServerCapabilities {
118            position_encoding: Some(position_encoding.into()),
119            code_action_provider: Some(types::CodeActionProviderCapability::Options(
120                CodeActionOptions {
121                    code_action_kinds: Some(
122                        SupportedCodeAction::all()
123                            .map(SupportedCodeAction::to_kind)
124                            .collect(),
125                    ),
126                    work_done_progress_options: WorkDoneProgressOptions {
127                        work_done_progress: Some(true),
128                    },
129                    resolve_provider: Some(true),
130                },
131            )),
132            workspace: Some(types::WorkspaceServerCapabilities {
133                workspace_folders: Some(WorkspaceFoldersServerCapabilities {
134                    supported: Some(true),
135                    change_notifications: Some(OneOf::Left(true)),
136                }),
137                file_operations: None,
138            }),
139            document_formatting_provider: Some(OneOf::Left(true)),
140            document_range_formatting_provider: Some(OneOf::Left(true)),
141            diagnostic_provider: Some(types::DiagnosticServerCapabilities::Options(
142                DiagnosticOptions {
143                    identifier: Some(crate::DIAGNOSTIC_NAME.into()),
144                    inter_file_dependencies: false,
145                    workspace_diagnostics: false,
146                    work_done_progress_options: WorkDoneProgressOptions {
147                        work_done_progress: Some(true),
148                    },
149                },
150            )),
151            execute_command_provider: Some(types::ExecuteCommandOptions {
152                commands: SupportedCommand::all()
153                    .map(|command| command.identifier().to_string())
154                    .collect(),
155                work_done_progress_options: WorkDoneProgressOptions {
156                    work_done_progress: Some(false),
157                },
158            }),
159            hover_provider: Some(types::HoverProviderCapability::Simple(true)),
160            text_document_sync: Some(TextDocumentSyncCapability::Options(
161                TextDocumentSyncOptions {
162                    open_close: Some(true),
163                    change: Some(TextDocumentSyncKind::INCREMENTAL),
164                    will_save: Some(false),
165                    will_save_wait_until: Some(false),
166                    ..Default::default()
167                },
168            )),
169            ..Default::default()
170        }
171    }
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
175pub(crate) enum SupportedCodeAction {
176    QuickFix,
177    SourceFixAll,
178}
179
180impl SupportedCodeAction {
181    fn all() -> impl Iterator<Item = Self> {
182        [Self::QuickFix, Self::SourceFixAll].into_iter()
183    }
184
185    fn to_kind(self) -> CodeActionKind {
186        match self {
187            Self::QuickFix => CodeActionKind::QUICKFIX,
188            Self::SourceFixAll => crate::SOURCE_FIX_ALL_SHUCK,
189        }
190    }
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
194pub(crate) enum SupportedCommand {
195    ApplyAutofix,
196}
197
198impl SupportedCommand {
199    fn all() -> impl Iterator<Item = Self> {
200        [Self::ApplyAutofix].into_iter()
201    }
202
203    fn identifier(self) -> &'static str {
204        match self {
205            Self::ApplyAutofix => "shuck.applyAutofix",
206        }
207    }
208}