diff --git a/crates/rome_cli/src/execute.rs b/crates/rome_cli/src/execute.rs index b8432e5917cf..1f1c01518d3a 100644 --- a/crates/rome_cli/src/execute.rs +++ b/crates/rome_cli/src/execute.rs @@ -3,7 +3,7 @@ use crate::{CliSession, Termination}; use rome_console::{markup, ConsoleExt}; use rome_fs::RomePath; use rome_service::workspace::{ - FeatureName, FixFileMode, FormatFileParams, OpenFileParams, SupportsFeatureParams, + FeatureName, FixFileMode, FormatFileParams, Language, OpenFileParams, SupportsFeatureParams, }; use std::path::PathBuf; @@ -137,6 +137,7 @@ pub(crate) fn execute_mode(mode: Execution, mut session: CliSession) -> Result<( path: rome_path.clone(), version: 0, content: content.into(), + language_hint: Language::default(), })?; let printed = workspace.format_file(FormatFileParams { path: rome_path })?; diff --git a/crates/rome_cli/src/traversal.rs b/crates/rome_cli/src/traversal.rs index 8b2e0ebf8a0f..ffa74bdf8c68 100644 --- a/crates/rome_cli/src/traversal.rs +++ b/crates/rome_cli/src/traversal.rs @@ -15,7 +15,9 @@ use rome_diagnostics::{ use rome_fs::{AtomicInterner, FileSystem, OpenOptions, PathInterner, RomePath}; use rome_fs::{TraversalContext, TraversalScope}; use rome_service::{ - workspace::{FeatureName, FileGuard, OpenFileParams, RuleCategories, SupportsFeatureParams}, + workspace::{ + FeatureName, FileGuard, Language, OpenFileParams, RuleCategories, SupportsFeatureParams, + }, Workspace, }; use std::{ @@ -571,6 +573,7 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes path: rome_path, version: 0, content: input.clone(), + language_hint: Language::default(), }, ) .with_file_id_and_code(file_id, "IO")?; diff --git a/crates/rome_js_syntax/src/source_type.rs b/crates/rome_js_syntax/src/source_type.rs index 0edc37958eae..32e87fd45c0b 100644 --- a/crates/rome_js_syntax/src/source_type.rs +++ b/crates/rome_js_syntax/src/source_type.rs @@ -1,5 +1,5 @@ use std::fmt::Display; -use std::path::Path; +use std::path::{Path, PathBuf}; /// Enum of the different ECMAScript standard versions. /// The versions are ordered in increasing order; The newest version comes last. @@ -177,15 +177,15 @@ impl TryFrom<&Path> for SourceType { fn try_from(path: &Path) -> Result { let file_name = path .file_name() - .expect("Can't read the file name") + .ok_or_else(|| SourceTypeError::MissingFileName(path.into()))? .to_str() - .expect("Can't read the file name"); + .ok_or_else(|| SourceTypeError::MissingFileName(path.into()))?; let extension = path .extension() - .expect("Can't read the file extension") + .ok_or_else(|| SourceTypeError::MissingFileExtension(path.into()))? .to_str() - .expect("Can't read the file extension"); + .ok_or_else(|| SourceTypeError::MissingFileExtension(path.into()))?; compute_source_type_from_path_or_extension(file_name, extension) } @@ -194,6 +194,10 @@ impl TryFrom<&Path> for SourceType { /// Errors around the construct of the source type #[derive(Debug)] pub enum SourceTypeError { + /// The path has no file name + MissingFileName(PathBuf), + /// The path has no file extension + MissingFileExtension(PathBuf), /// The source type is unknown UnknownExtension(String), } @@ -203,6 +207,12 @@ impl std::error::Error for SourceTypeError {} impl Display for SourceTypeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + SourceTypeError::MissingFileName(path) => { + write!(f, "The path {path:?} has no file name") + } + SourceTypeError::MissingFileExtension(path) => { + write!(f, "The path {path:?} has no file extension") + } SourceTypeError::UnknownExtension(extension) => { write!(f, "The parser can't parse the extension '{extension}' yet") } diff --git a/crates/rome_lsp/src/documents.rs b/crates/rome_lsp/src/documents.rs index d7c24d963570..a71bab3d5789 100644 --- a/crates/rome_lsp/src/documents.rs +++ b/crates/rome_lsp/src/documents.rs @@ -1,32 +1,5 @@ -use anyhow::bail; - use crate::line_index::LineIndex; -/// Internal representation of supported [language identifiers] -/// -/// [language identifiers]: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum EditorLanguage { - JavaScript, - JavaScriptReact, - TypeScript, - TypeScriptReact, -} - -impl TryFrom<&str> for EditorLanguage { - type Error = anyhow::Error; - - fn try_from(value: &str) -> Result { - match value { - "javascript" => Ok(EditorLanguage::JavaScript), - "javascriptreact" => Ok(EditorLanguage::JavaScriptReact), - "typescript" => Ok(EditorLanguage::TypeScript), - "typescriptreact" => Ok(EditorLanguage::TypeScriptReact), - _ => bail!("Unsupported language: {}", value), - } - } -} - /// Represents an open [`textDocument`]. Can be cheaply cloned. /// /// [`textDocument`]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentItem diff --git a/crates/rome_lsp/src/handlers/text_document.rs b/crates/rome_lsp/src/handlers/text_document.rs index b51b949023d7..50d307b4d486 100644 --- a/crates/rome_lsp/src/handlers/text_document.rs +++ b/crates/rome_lsp/src/handlers/text_document.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Result}; -use rome_service::workspace::{ChangeFileParams, CloseFileParams, OpenFileParams}; +use rome_service::workspace::{ChangeFileParams, CloseFileParams, Language, OpenFileParams}; use tower_lsp::lsp_types; use tracing::error; @@ -14,6 +14,7 @@ pub(crate) async fn did_open( let url = params.text_document.uri; let version = params.text_document.version; let content = params.text_document.text; + let language_hint = Language::from_language_id(¶ms.text_document.language_id); let rome_path = session.file_path(&url); let doc = Document::new(version, &content); @@ -22,6 +23,7 @@ pub(crate) async fn did_open( path: rome_path, version, content, + language_hint, })?; session.insert_document(url.clone(), doc); diff --git a/crates/rome_lsp/tests/server.rs b/crates/rome_lsp/tests/server.rs index 2cf9d2004300..f7edef4c6272 100644 --- a/crates/rome_lsp/tests/server.rs +++ b/crates/rome_lsp/tests/server.rs @@ -273,6 +273,76 @@ async fn document_lifecycle() -> Result<()> { Ok(()) } +#[tokio::test] +async fn document_no_extension() -> Result<()> { + let factory = ServerFactory::default(); + let (service, client) = factory.create().into_inner(); + let (stream, sink) = client.split(); + let mut server = Server::new(service); + + let reader = tokio::spawn(client_handler(stream, sink)); + + server.initialize().await?; + server.initialized().await?; + + server + .notify( + "textDocument/didOpen", + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: Url::parse("test://workspace/document")?, + language_id: String::from("javascript"), + version: 0, + text: String::from("statement()"), + }, + }, + ) + .await?; + + let res: Option> = server + .request( + "textDocument/formatting", + "formatting", + DocumentFormattingParams { + text_document: TextDocumentIdentifier { + uri: Url::parse("test://workspace/document")?, + }, + options: FormattingOptions { + tab_size: 4, + insert_spaces: false, + properties: HashMap::default(), + trim_trailing_whitespace: None, + insert_final_newline: None, + trim_final_newlines: None, + }, + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + }, + ) + .await? + .context("formatting returned None")?; + + let edits = res.context("formatting did not return an edit list")?; + assert!(!edits.is_empty(), "formatting returned an empty edit list"); + + server + .notify( + "textDocument/didClose", + DidCloseTextDocumentParams { + text_document: TextDocumentIdentifier { + uri: Url::parse("test://workspace/document")?, + }, + }, + ) + .await?; + + server.shutdown().await?; + reader.abort(); + + Ok(()) +} + #[tokio::test] async fn pull_quick_fixes() -> Result<()> { let factory = ServerFactory::default(); diff --git a/crates/rome_service/src/file_handlers/javascript.rs b/crates/rome_service/src/file_handlers/javascript.rs index a4f3315fdae4..9301855d1c69 100644 --- a/crates/rome_service/src/file_handlers/javascript.rs +++ b/crates/rome_service/src/file_handlers/javascript.rs @@ -26,7 +26,7 @@ use super::{ AnalyzerCapabilities, DebugCapabilities, ExtensionHandler, FormatterCapabilities, Mime, ParserCapabilities, }; -use crate::file_handlers::FixAllParams; +use crate::file_handlers::{FixAllParams, Language as LanguageId}; use indexmap::IndexSet; use rome_console::codespan::Severity; use std::borrow::Cow; @@ -106,11 +106,16 @@ impl ExtensionHandler for JsFileHandler { } } -fn parse(rome_path: &RomePath, text: &str) -> AnyParse { +fn parse(rome_path: &RomePath, language_hint: LanguageId, text: &str) -> AnyParse { let file_id = rome_path.file_id(); let source_type = - SourceType::try_from(rome_path.as_path()).unwrap_or_else(|_| SourceType::js_module()); + SourceType::try_from(rome_path.as_path()).unwrap_or_else(|_| match language_hint { + LanguageId::JavaScriptReact => SourceType::jsx(), + LanguageId::TypeScript => SourceType::ts(), + LanguageId::TypeScriptReact => SourceType::tsx(), + _ => SourceType::js_module(), + }); let parse = rome_js_parser::parse(text, file_id, source_type); AnyParse::from(parse) diff --git a/crates/rome_service/src/file_handlers/mod.rs b/crates/rome_service/src/file_handlers/mod.rs index c8e74d3e6087..579bbd24b3e2 100644 --- a/crates/rome_service/src/file_handlers/mod.rs +++ b/crates/rome_service/src/file_handlers/mod.rs @@ -23,34 +23,81 @@ use crate::workspace::FixFileMode; pub use javascript::JsFormatSettings; /// Supported languages by Rome -#[derive(Debug, PartialEq)] -pub(crate) enum Language { - /// JavaScript, TypeScript, JSX, TSX +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub enum Language { + /// JavaScript JavaScript, + /// JSX + JavaScriptReact, + /// TypeScript + TypeScript, + /// TSX + TypeScriptReact, /// JSON Json, /// Any language that is not supported + #[default] Unknown, } -impl From<&str> for Language { - fn from(s: &str) -> Self { +impl Language { + /// Returns the language corresponding to this file extension + pub fn from_extension(s: &str) -> Self { match s.to_lowercase().as_str() { - "js" | "ts" | "jsx" | "tsx" | "mjs" | "cjs" | "cts" | "mts" => Language::JavaScript, + "js" | "mjs" | "cjs" => Language::JavaScript, + "jsx" => Language::JavaScriptReact, + "ts" | "mts" | "cts" => Language::TypeScript, + "tsx" => Language::TypeScriptReact, "json" => Language::Json, _ => Language::Unknown, } } -} -impl From<&OsStr> for Language { - fn from(s: &OsStr) -> Self { - match s.to_str().unwrap() { - "js" | "ts" | "jsx" | "tsx" | "mjs" | "cjs" | "cts" | "mts" => Language::JavaScript, + /// Returns the language corresponding to this language ID + /// + /// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem + /// for a list of language identifiers + pub fn from_language_id(s: &str) -> Self { + match s.to_lowercase().as_str() { + "javascript" => Language::JavaScript, + "typescript" => Language::TypeScript, + "javascriptreact" => Language::JavaScriptReact, + "typescriptreact" => Language::TypeScriptReact, "json" => Language::Json, _ => Language::Unknown, } } + + /// Returns the language if it's not unknown, otherwise returns `other`. + /// + /// # Examples + /// + /// ``` + /// # use rome_service::workspace::Language; + /// let x = Language::JavaScript; + /// let y = Language::Unknown; + /// assert_eq!(x.or(y), Language::JavaScript); + /// + /// let x = Language::Unknown; + /// let y = Language::JavaScript; + /// assert_eq!(x.or(y), Language::JavaScript); + /// + /// let x = Language::JavaScript; + /// let y = Language::Json; + /// assert_eq!(x.or(y), Language::JavaScript); + /// + /// let x = Language::Unknown; + /// let y = Language::Unknown; + /// assert_eq!(x.or(y), Language::Unknown); + /// ``` + pub fn or(self, other: Language) -> Language { + if self != Language::Unknown { + self + } else { + other + } + } } // TODO: The Css variant is unused at the moment @@ -89,7 +136,7 @@ pub(crate) struct Capabilities { pub(crate) formatter: FormatterCapabilities, } -type Parse = fn(&RomePath, &str) -> AnyParse; +type Parse = fn(&RomePath, Language, &str) -> AnyParse; #[derive(Default)] pub(crate) struct ParserCapabilities { @@ -186,17 +233,25 @@ impl Features { } /// Return a [Language] from a string - fn get_language(rome_path: &RomePath) -> Language { - match rome_path.extension() { - Some(file_extension) => file_extension.into(), - None => Language::Unknown, - } + pub(crate) fn get_language(rome_path: &RomePath) -> Language { + rome_path + .extension() + .and_then(OsStr::to_str) + .map(Language::from_extension) + .unwrap_or_default() } /// Returns the [Capabilities] associated with a [RomePath] - pub(crate) fn get_capabilities(&self, rome_path: &RomePath) -> Capabilities { - match Self::get_language(rome_path) { - Language::JavaScript => self.js.capabilities(), + pub(crate) fn get_capabilities( + &self, + rome_path: &RomePath, + language_hint: Language, + ) -> Capabilities { + match Self::get_language(rome_path).or(language_hint) { + Language::JavaScript + | Language::JavaScriptReact + | Language::TypeScript + | Language::TypeScriptReact => self.js.capabilities(), Language::Json => self.json.capabilities(), Language::Unknown => self.unknown.capabilities(), } diff --git a/crates/rome_service/src/lib.rs b/crates/rome_service/src/lib.rs index cb0beaaf39c2..a6e49651919f 100644 --- a/crates/rome_service/src/lib.rs +++ b/crates/rome_service/src/lib.rs @@ -5,6 +5,7 @@ use rome_js_analyze::utils::rename::RenameError; use rome_js_analyze::RuleError; use serde::{Deserialize, Serialize}; use std::error::Error; +use std::ffi::OsStr; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; @@ -21,6 +22,7 @@ pub use crate::configuration::{ create_config, load_config, Configuration, ConfigurationError, RuleConfiguration, Rules, }; pub use crate::file_handlers::JsFormatSettings; +use crate::file_handlers::Language; pub use crate::workspace::Workspace; /// This is the main entrypoint of the application. @@ -41,9 +43,9 @@ pub enum RomeError { DirtyWorkspace, /// The file does not exist in the [Workspace] NotFound, - /// A file is not supported. It contains the extension of the file + /// A file is not supported. It contains the language and path of the file /// Use this error if Rome is trying to process a file that Rome can't understand - SourceFileNotSupported(RomePath), + SourceFileNotSupported(Language, RomePath), /// The formatter encountered an error while formatting the file FormatError(FormatError), /// The file could not be formatted since it has syntax errors and `format_with_errors` is disabled @@ -71,15 +73,21 @@ impl Debug for RomeError { impl Display for RomeError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - RomeError::SourceFileNotSupported(path) => { - let ext = path.extension().and_then(|ext| ext.to_str()); - - if let Some(ext) = ext { - write!(f, "Rome doesn't support the file extension {ext:?} yet") + RomeError::SourceFileNotSupported(language, path) => { + if *language != Language::Unknown { + write!( + f, + "Rome doesn't support this feature for the language {language:?}" + ) + } else if let Some(ext) = path.extension().and_then(OsStr::to_str) { + write!( + f, + "Rome could not determine the language for the file extension {ext:?}" + ) } else { write!( f, - "Rome can't process the file because it doesn't have a clear extension" + "Rome could not determine the language for the file {path:?} because it doesn't have a clear extension" ) } } diff --git a/crates/rome_service/src/workspace.rs b/crates/rome_service/src/workspace.rs index 5aae26795f64..1849d40dc265 100644 --- a/crates/rome_service/src/workspace.rs +++ b/crates/rome_service/src/workspace.rs @@ -64,6 +64,7 @@ use rome_text_edit::Indel; use std::{borrow::Cow, panic::RefUnwindSafe, sync::Arc}; pub use self::client::WorkspaceTransport; +pub use crate::file_handlers::Language; mod client; pub(crate) mod server; @@ -94,6 +95,8 @@ pub struct OpenFileParams { pub path: RomePath, pub content: String, pub version: i32, + #[serde(default)] + pub language_hint: Language, } #[derive(serde::Serialize, serde::Deserialize)] diff --git a/crates/rome_service/src/workspace/server.rs b/crates/rome_service/src/workspace/server.rs index 885bc310aa5a..be1bcef0017b 100644 --- a/crates/rome_service/src/workspace/server.rs +++ b/crates/rome_service/src/workspace/server.rs @@ -5,7 +5,7 @@ use super::{ PullDiagnosticsParams, PullDiagnosticsResult, RenameResult, SupportsFeatureParams, UpdateSettingsParams, }; -use crate::file_handlers::FixAllParams; +use crate::file_handlers::{Capabilities, FixAllParams, Language}; use crate::{ file_handlers::Features, settings::{SettingsHandle, WorkspaceSettings}, @@ -43,6 +43,7 @@ impl RefUnwindSafe for WorkspaceServer {} pub(crate) struct Document { pub(crate) content: String, pub(crate) version: i32, + pub(crate) language_hint: Language, } /// Language-independent cache entry for a parsed file @@ -110,6 +111,31 @@ impl WorkspaceServer { SettingsHandle::new(&self.settings) } + /// Get the supported capabilities for a given file path + fn get_capabilities(&self, path: &RomePath) -> Capabilities { + let language = self + .documents + .get(path) + .map(|doc| doc.language_hint) + .unwrap_or_default(); + + self.features.get_capabilities(path, language) + } + + /// Return an error factory function for unsupported features at a given path + fn build_capability_error<'a>(&'a self, path: &'a RomePath) -> impl FnOnce() -> RomeError + 'a { + move || { + let language_hint = self + .documents + .get(path) + .map(|doc| doc.language_hint) + .unwrap_or_default(); + + let language = Features::get_language(path).or(language_hint); + RomeError::SourceFileNotSupported(language, path.clone()) + } + } + /// Get the parser result for a given file /// /// Returns and error if no file exists in the workspace with this path or @@ -121,13 +147,13 @@ impl WorkspaceServer { let rome_path = entry.key(); let document = self.documents.get(rome_path).ok_or(RomeError::NotFound)?; - let capabilities = self.features.get_capabilities(rome_path); + let capabilities = self.get_capabilities(rome_path); let parse = capabilities .parser .parse - .ok_or_else(|| RomeError::SourceFileNotSupported(rome_path.clone()))?; + .ok_or_else(self.build_capability_error(rome_path))?; - let parsed = parse(rome_path, &document.content); + let parsed = parse(rome_path, document.language_hint, &document.content); Ok(entry.insert(parsed).clone()) } @@ -137,7 +163,7 @@ impl WorkspaceServer { impl Workspace for WorkspaceServer { fn supports_feature(&self, params: SupportsFeatureParams) -> bool { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let settings = self.settings.read().unwrap(); match params.feature { FeatureName::Format => { @@ -166,6 +192,7 @@ impl Workspace for WorkspaceServer { Document { content: params.content, version: params.version, + language_hint: params.language_hint, }, ); Ok(()) @@ -175,11 +202,11 @@ impl Workspace for WorkspaceServer { &self, params: GetSyntaxTreeParams, ) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let debug_syntax_tree = capabilities .debug .debug_syntax_tree - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let printed = debug_syntax_tree(¶ms.path, parse); @@ -191,11 +218,11 @@ impl Workspace for WorkspaceServer { &self, params: GetControlFlowGraphParams, ) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let debug_control_flow = capabilities .debug .debug_control_flow - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let printed = debug_control_flow(¶ms.path, parse, params.cursor); @@ -204,11 +231,11 @@ impl Workspace for WorkspaceServer { } fn get_formatter_ir(&self, params: GetFormatterIRParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let debug_formatter_ir = capabilities .debug .debug_formatter_ir - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings(); @@ -250,11 +277,11 @@ impl Workspace for WorkspaceServer { &self, params: PullDiagnosticsParams, ) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let lint = capabilities .analyzer .lint - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings.read().unwrap(); @@ -280,11 +307,11 @@ impl Workspace for WorkspaceServer { /// Retrieves the list of code actions available for a given cursor /// position within a file fn pull_actions(&self, params: PullActionsParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let code_actions = capabilities .analyzer .code_actions - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; @@ -297,11 +324,11 @@ impl Workspace for WorkspaceServer { /// Runs the given file through the formatter using the provided options /// and returns the resulting source code fn format_file(&self, params: FormatFileParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let format = capabilities .formatter .format - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings(); @@ -314,11 +341,11 @@ impl Workspace for WorkspaceServer { } fn format_range(&self, params: FormatRangeParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let format_range = capabilities .formatter .format_range - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings(); @@ -331,11 +358,11 @@ impl Workspace for WorkspaceServer { } fn format_on_type(&self, params: FormatOnTypeParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let format_on_type = capabilities .formatter .format_on_type - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings(); @@ -348,11 +375,11 @@ impl Workspace for WorkspaceServer { } fn fix_file(&self, params: super::FixFileParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let fix_all = capabilities .analyzer .fix_all - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let settings = self.settings.read().unwrap(); @@ -366,11 +393,11 @@ impl Workspace for WorkspaceServer { } fn rename(&self, params: super::RenameParams) -> Result { - let capabilities = self.features.get_capabilities(¶ms.path); + let capabilities = self.get_capabilities(¶ms.path); let rename = capabilities .analyzer .rename - .ok_or_else(|| RomeError::SourceFileNotSupported(params.path.clone()))?; + .ok_or_else(self.build_capability_error(¶ms.path))?; let parse = self.get_parse(params.path.clone())?; let result = rename(¶ms.path, parse, params.symbol_at, params.new_name)?; diff --git a/editors/vscode/src/main.ts b/editors/vscode/src/main.ts index 640f2a9b1f1a..000117357215 100644 --- a/editors/vscode/src/main.ts +++ b/editors/vscode/src/main.ts @@ -48,10 +48,10 @@ export async function activate(context: ExtensionContext) { const traceOutputChannel = window.createOutputChannel("Rome Trace"); const documentSelector: DocumentFilter[] = [ - { scheme: "file", language: "javascript" }, - { scheme: "file", language: "typescript" }, - { scheme: "file", language: "javascriptreact" }, - { scheme: "file", language: "typescriptreact" }, + { language: "javascript" }, + { language: "typescript" }, + { language: "javascriptreact" }, + { language: "typescriptreact" }, ]; const clientOptions: LanguageClientOptions = { diff --git a/editors/vscode/src/status_bar.ts b/editors/vscode/src/status_bar.ts new file mode 100644 index 000000000000..d14359a07ddd --- /dev/null +++ b/editors/vscode/src/status_bar.ts @@ -0,0 +1,115 @@ +import { StatusBarAlignment, StatusBarItem, ThemeColor, window } from "vscode"; +import { State } from "vscode-languageclient"; +import { Commands } from "./commands"; + +enum Status { + Pending = "refresh", + Ready = "check", + Inactive = "eye-closed", + Warning = "warning", + Error = "error", +} + +export class StatusBar { + private statusBarItem: StatusBarItem; + + private serverState: State = State.Starting; + private isActive: boolean = false; + + constructor() { + this.statusBarItem = window.createStatusBarItem( + "rome.status", + StatusBarAlignment.Right, + -1, + ); + + this.statusBarItem.name = "Rome"; + this.statusBarItem.command = Commands.ServerStatus; + this.update(); + } + + public setServerState(state: State) { + this.serverState = state; + this.update(); + } + + public setActive(isActive: boolean) { + this.isActive = isActive; + this.update(); + } + + private update() { + let status: Status; + if (this.serverState === State.Running) { + if (this.isActive) { + status = Status.Ready; + } else { + status = Status.Inactive; + } + } else if (this.serverState === State.Starting) { + status = Status.Pending; + } else { + status = Status.Error; + } + + this.statusBarItem.text = `$(${status}) Rome`; + + switch (status) { + case Status.Pending: { + this.statusBarItem.tooltip = "Rome is initializing ..."; + break; + } + case Status.Ready: { + this.statusBarItem.tooltip = "Rome is active"; + break; + } + case Status.Inactive: { + this.statusBarItem.tooltip = + "The current file is not supported or ignored by Rome"; + break; + } + // @ts-expect-error Reserved for future use + case Status.Warning: { + this.statusBarItem.tooltip = undefined; + break; + } + case Status.Error: { + this.statusBarItem.tooltip = "Rome encountered a fatal error"; + break; + } + } + + switch (status) { + case Status.Error: { + this.statusBarItem.color = new ThemeColor( + "statusBarItem.errorForeground", + ); + this.statusBarItem.backgroundColor = new ThemeColor( + "statusBarItem.errorBackground", + ); + break; + } + // @ts-expect-error Reserved for future use + case Status.Warning: { + this.statusBarItem.color = new ThemeColor( + "statusBarItem.warningForeground", + ); + this.statusBarItem.backgroundColor = new ThemeColor( + "statusBarItem.warningBackground", + ); + break; + } + default: { + this.statusBarItem.color = undefined; + this.statusBarItem.backgroundColor = undefined; + break; + } + } + + this.statusBarItem.show(); + } + + public hide() { + this.statusBarItem.hide(); + } +} diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 947ae0bb422d..1c7ba5a98544 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -122,9 +122,20 @@ export interface Ts { } export interface OpenFileParams { content: string; + language_hint?: Language; path: RomePath; version: number; } +/** + * Supported languages by Rome + */ +export type Language = + | "JavaScript" + | "JavaScriptReact" + | "TypeScript" + | "TypeScriptReact" + | "Json" + | "Unknown"; export interface ChangeFileParams { content: string; path: RomePath;