Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 34 additions & 59 deletions compiler-executable/iris-lsp-workspace/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ use iris_analysis::position::PositionEncoding;
use lsp_types::*;

pub(crate) fn negotiate_analyzer_capabilities(
parameters: &InitializeParams,
capabilities: &ClientCapabilities,
) -> AnalyzerCapabilities {
let workspace_edit = parameters
.capabilities
.workspace
.as_ref()
.and_then(|workspace| workspace.workspace_edit.as_ref());
let honors_rename_annotations = parameters
.capabilities
let workspace_edit =
capabilities.workspace.as_ref().and_then(|workspace| workspace.workspace_edit.as_ref());
let honors_rename_annotations = capabilities
.text_document
.as_ref()
.and_then(|text_document| text_document.rename.as_ref())
Expand All @@ -28,8 +24,7 @@ pub(crate) fn negotiate_analyzer_capabilities(
if change_annotations {
negotiated = negotiated.with_change_annotations();
}
let markdown_hover = parameters
.capabilities
let markdown_hover = capabilities
.text_document
.as_ref()
.and_then(|text_document| text_document.hover.as_ref())
Expand All @@ -46,12 +41,9 @@ pub(crate) fn negotiate_analyzer_capabilities(
negotiated
}

pub(crate) fn negotiate_position_encoding(parameters: &InitializeParams) -> PositionEncoding {
let Some(encodings) = parameters
.capabilities
.general
.as_ref()
.and_then(|general| general.position_encodings.as_ref())
pub(crate) fn negotiate_position_encoding(capabilities: &ClientCapabilities) -> PositionEncoding {
let Some(encodings) =
capabilities.general.as_ref().and_then(|general| general.position_encodings.as_ref())
else {
return PositionEncoding::Utf16;
};
Expand Down Expand Up @@ -141,105 +133,88 @@ mod tests {

use super::*;

fn initialize_parameters(
fn client_capabilities(
position_encodings: Option<Vec<PositionEncodingKind>>,
) -> InitializeParams {
InitializeParams {
capabilities: ClientCapabilities {
general: Some(GeneralClientCapabilities {
position_encodings,
..GeneralClientCapabilities::default()
}),
..ClientCapabilities::default()
},
..InitializeParams::default()
) -> ClientCapabilities {
ClientCapabilities {
general: Some(GeneralClientCapabilities {
position_encodings,
..GeneralClientCapabilities::default()
}),
..ClientCapabilities::default()
}
}

#[test]
fn defaults_to_utf16_without_client_preference() {
let parameters = InitializeParams::default();
let capabilities = ClientCapabilities::default();

let encoding = negotiate_position_encoding(&parameters);
let encoding = negotiate_position_encoding(&capabilities);
assert_eq!(encoding, PositionEncoding::Utf16);
}

#[test]
fn prefers_utf8_when_available() {
let parameters = initialize_parameters(Some(vec![
let capabilities = client_capabilities(Some(vec![
PositionEncodingKind::UTF32,
PositionEncodingKind::UTF16,
PositionEncodingKind::UTF8,
]));

let encoding = negotiate_position_encoding(&parameters);
let encoding = negotiate_position_encoding(&capabilities);
assert_eq!(encoding, PositionEncoding::Utf8);
}

#[test]
fn falls_back_to_utf16_before_utf32() {
let parameters = initialize_parameters(Some(vec![
let capabilities = client_capabilities(Some(vec![
PositionEncodingKind::UTF32,
PositionEncodingKind::UTF16,
]));

let encoding = negotiate_position_encoding(&parameters);
let encoding = negotiate_position_encoding(&capabilities);
assert_eq!(encoding, PositionEncoding::Utf16);
}

#[test]
fn supports_utf32_when_it_is_the_only_known_option() {
let parameters = initialize_parameters(Some(vec![PositionEncodingKind::UTF32]));
let capabilities = client_capabilities(Some(vec![PositionEncodingKind::UTF32]));

let encoding = negotiate_position_encoding(&parameters);
let encoding = negotiate_position_encoding(&capabilities);
assert_eq!(encoding, PositionEncoding::Utf32);
}

#[test]
fn negotiates_hover_content_format() {
let mut parameters = InitializeParams::default();
let mut capabilities = ClientCapabilities::default();
assert_eq!(
negotiate_analyzer_capabilities(&parameters).hover_format(),
negotiate_analyzer_capabilities(&capabilities).hover_format(),
MarkupKind::PlainText
);

parameters.capabilities.text_document = Some(TextDocumentClientCapabilities {
capabilities.text_document = Some(TextDocumentClientCapabilities {
hover: Some(HoverClientCapabilities {
content_format: Some(vec![MarkupKind::PlainText]),
..HoverClientCapabilities::default()
}),
..TextDocumentClientCapabilities::default()
});
assert_eq!(
negotiate_analyzer_capabilities(&parameters).hover_format(),
negotiate_analyzer_capabilities(&capabilities).hover_format(),
MarkupKind::PlainText
);

parameters
.capabilities
.text_document
.as_mut()
.unwrap()
.hover
.as_mut()
.unwrap()
.content_format = Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]);
capabilities.text_document.as_mut().unwrap().hover.as_mut().unwrap().content_format =
Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]);
assert_eq!(
negotiate_analyzer_capabilities(&parameters).hover_format(),
negotiate_analyzer_capabilities(&capabilities).hover_format(),
MarkupKind::PlainText
);

parameters
.capabilities
.text_document
.as_mut()
.unwrap()
.hover
.as_mut()
.unwrap()
.content_format = Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]);
capabilities.text_document.as_mut().unwrap().hover.as_mut().unwrap().content_format =
Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]);
assert_eq!(
negotiate_analyzer_capabilities(&parameters).hover_format(),
negotiate_analyzer_capabilities(&capabilities).hover_format(),
MarkupKind::Markdown
);
}
Expand Down
18 changes: 14 additions & 4 deletions compiler-executable/iris-lsp-workspace/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use iris_lsp_server::{
Answer, ControlMessage, OrderedMessage, Rejection, SettingsResponse, WorkspaceEvent,
WorkspaceEventSender, WorkspaceFailure, WorkspaceReceivers,
};
use lsp_types::{InitializeParams, Uri, WorkspaceFolders};
use lsp_types::{ClientCapabilities, Uri, WorkspaceFolders};
use serde::Deserialize;
use serde_json::{Value, json};
use tokio::sync::{mpsc, oneshot};
use tokio::task;
Expand Down Expand Up @@ -71,6 +72,14 @@ struct Session {
analyzer_capabilities: AnalyzerCapabilities,
}

/// Decode only workspace-owned fields; `iris-lsp-server` interprets `processId`.
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct InitializeParams {
capabilities: ClientCapabilities,
workspace_folders: Option<WorkspaceFolders>,
}

const WORKSPACE_LOADING: &str = "Workspace is loading";
const WORKSPACE_FAILED: &str = "Workspace preparation failed";
const WORKSPACE_CANCELLED: &str = "Workspace preparation cancelled";
Expand Down Expand Up @@ -278,9 +287,10 @@ impl Actor {
let parameters = serde_json::from_value::<InitializeParams>(params).map_err(|error| {
Rejection::InvalidParams(format!("Failed to deserialize parameters: {error}"))
})?;
self.session.position_encoding = negotiate_position_encoding(&parameters);
self.session.analyzer_capabilities = negotiate_analyzer_capabilities(&parameters);
let scope = match parameters.workspace_folders_initialize_params.workspace_folders {
self.session.position_encoding = negotiate_position_encoding(&parameters.capabilities);
self.session.analyzer_capabilities =
negotiate_analyzer_capabilities(&parameters.capabilities);
let scope = match parameters.workspace_folders {
Some(WorkspaceFolders::WorkspaceFolderList(folders)) => {
folders.first().map(|folder| Uri::clone(&folder.uri))
}
Expand Down
23 changes: 22 additions & 1 deletion compiler-executable/iris-lsp-workspace/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use iris_lsp_server::{
Answer, ControlMessage, OrderedMessage, Rejection, SettingsResponse, WorkspaceEvent,
WorkspaceEventSender, WorkspaceFailure, WorkspaceSenders,
};
use lsp_types::{Position, Range, Uri};
use lsp_types::{InitializeResult, Position, PositionEncodingKind, Range, Uri};
use parking_lot::Mutex;
use serde_json::{Value, json};
use tempfile::TempDir;
Expand Down Expand Up @@ -307,6 +307,27 @@ fn module(name: &str, value: &str) -> String {
format!("module {name} where\n{value} = 1\n")
}

#[tokio::test]
async fn an_out_of_range_process_id_does_not_prevent_initialization() {
let (events, _event_receiver) = WorkspaceEventSender::channel();
let (senders, receivers) = WorkspaceSenders::channel();
let actor = Actor::new(config(1), events);
let task = tokio::spawn(actor.run(receivers));
let initialize = senders.initialize(json!({
"processId": 2_147_483_648_i64,
"capabilities": {"general": {"positionEncodings": ["utf-8"]}},
"workspaceFolders": null
}));

let result = answer(initialize).await.unwrap();
let result = serde_json::from_value::<InitializeResult>(result).unwrap();
assert_eq!(result.capabilities.position_encoding, Some(PositionEncodingKind::UTF8));
assert_eq!(result.server_info.unwrap().name, "iris");

drop(senders);
tokio::time::timeout(PATIENCE, task).await.unwrap().unwrap().unwrap();
}

#[tokio::test]
async fn a_change_wins_over_running_and_waiting_analysis() {
// With one permit: a long request holds the permit, a second request waits for it, then a
Expand Down
Loading