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}