mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Intelephense checked PHP code against its default 8.5, whatever version the project targets. Settings › Languages & Frameworks › PHP gets a PHP language level, Auto (default: composer.json config.platform.php, else the lowest version require.php allows) or 5.6–8.5, sent as environment.phpVersion; nothing is sent without a requirement. Intelephense applies the version only to documents it parses after reading its configuration, so servers that opt in (resync-on-configuration, Intelephense only) get their open documents again right after workspace/configuration is answered (new jsonrpc Handler::after_response), and Intelephense keeps one index per level because it fixes the built-in functions when it indexes. A change to a root or workspace member composer.json reconfigures the servers. Co-authored-by: DanielADK <18214958+DanielADK@users.noreply.github.com>
303 lines
11 KiB
Rust
303 lines
11 KiB
Rust
//! Language registry and syntax-tree powered structure.
|
||
//!
|
||
//! Every language is described by `languages/<id>.toml` at the repo root
|
||
//! (embedded at build time). Tree-sitter gives zero-config outline, folding
|
||
//! and symbol tags; LSP/DAP configs are consumed by `ide-lsp` / `ide-dap`.
|
||
|
||
use ide_core::{LanguageId, Range, SymbolKind};
|
||
use serde::{Deserialize, Serialize};
|
||
use specta::Type;
|
||
use std::collections::BTreeMap;
|
||
use std::path::{Path, PathBuf};
|
||
|
||
mod markers;
|
||
pub mod plugin_point;
|
||
mod registry;
|
||
mod syntax;
|
||
pub use markers::marked_root;
|
||
pub use plugin_point::PluginLanguages;
|
||
pub use registry::{FileTypes, LanguageRegistry};
|
||
pub use syntax::{
|
||
CSS_CLASS, CSS_ID, CSS_VARIABLE, ConfigurationKey, ExternalFold, ExternalSymbol, ExternalSyntax, ExternalToken,
|
||
FileReference, Literal, LiteralKind, MAX_CACHED_TREES, MAX_PARSE_BYTES, ParsedTree, SpellingKind, SpellingRegion,
|
||
Syntax, SyntaxCache, SyntaxCacheStats, SyntaxHighlightKind, SyntaxHighlights, TreeError, WebReference,
|
||
WebReferenceKind, merge_visibility, set_external_syntax,
|
||
};
|
||
|
||
/// Parsed form of `languages/<id>.toml`. Backend-only; the UI gets [`LanguageInfo`].
|
||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
#[serde(rename_all = "kebab-case")]
|
||
pub struct LanguageConfig {
|
||
pub id: LanguageId,
|
||
pub name: String,
|
||
#[serde(default)]
|
||
pub extensions: Vec<String>,
|
||
/// Exact file names, e.g. `Cargo.lock`, `Dockerfile`.
|
||
#[serde(default)]
|
||
pub filenames: Vec<String>,
|
||
/// First-line regex, e.g. shebang `^#!.*\bpython`.
|
||
#[serde(default)]
|
||
pub first_line: Option<String>,
|
||
#[serde(default)]
|
||
pub line_comment: Option<String>,
|
||
#[serde(default)]
|
||
pub block_comment: Option<(String, String)>,
|
||
#[serde(default)]
|
||
pub indent: Option<IndentConfig>,
|
||
/// Name of the CodeMirror/Lezer language the UI should load for instant highlighting.
|
||
#[serde(default)]
|
||
pub ui_mode: Option<String>,
|
||
/// Modes replacing `ui_mode` in a framework's projects (Angular templates);
|
||
/// the first one whose root marker is found applies.
|
||
#[serde(default)]
|
||
pub framework_ui_modes: Vec<FrameworkUiMode>,
|
||
#[serde(default)]
|
||
pub tree_sitter: Option<String>,
|
||
#[serde(default)]
|
||
pub language_servers: Vec<LanguageServerConfig>,
|
||
#[serde(default)]
|
||
pub debugger: Option<DebuggerConfig>,
|
||
/// External formatter used when no language server can format, or selected in settings.
|
||
#[serde(default)]
|
||
pub formatter: Option<FormatterConfig>,
|
||
/// Content of a new scratch file; `$END$` marks where the caret goes.
|
||
#[serde(default)]
|
||
pub scratch_template: Option<String>,
|
||
}
|
||
|
||
/// An external formatting tool and its language-specific parser.
|
||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
#[serde(rename_all = "kebab-case")]
|
||
pub struct FormatterConfig {
|
||
pub tool: String,
|
||
pub parser: String,
|
||
#[serde(default)]
|
||
pub range: bool,
|
||
}
|
||
|
||
/// A CodeMirror mode for the files of a framework's projects.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "kebab-case")]
|
||
pub struct FrameworkUiMode {
|
||
pub ui_mode: String,
|
||
/// Files whose presence in a directory above a file, up to the project
|
||
/// folder, marks the framework's project, e.g. `angular.json`; matched like
|
||
/// language servers' `root-markers`.
|
||
pub root_markers: Vec<String>,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all(serialize = "camelCase", deserialize = "kebab-case"))]
|
||
pub struct IndentConfig {
|
||
pub tab_size: u32,
|
||
pub insert_spaces: bool,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "kebab-case")]
|
||
pub struct LanguageServerConfig {
|
||
/// Stable name, e.g. `rust-analyzer`.
|
||
pub name: String,
|
||
pub command: String,
|
||
#[serde(default)]
|
||
pub args: Vec<String>,
|
||
#[serde(default)]
|
||
pub env: BTreeMap<String, String>,
|
||
/// Files whose presence marks a workspace root, e.g. `Cargo.toml`;
|
||
/// `file#table` requires the TOML file to contain that dotted table, e.g.
|
||
/// `pyproject.toml#tool.ruff`.
|
||
#[serde(default)]
|
||
pub root_markers: Vec<String>,
|
||
/// Serve only files below a root marker, not the whole project: a framework's
|
||
/// server (Angular) next to the language's own one.
|
||
#[serde(default)]
|
||
pub require_root_marker: bool,
|
||
/// JSON passed as `initializationOptions`.
|
||
#[serde(default)]
|
||
pub initialization_options: Option<toml::Value>,
|
||
/// Answers `workspace/configuration`; without it the sections are looked up
|
||
/// in `initialization_options`.
|
||
#[serde(default)]
|
||
pub settings: Option<serde_json::Value>,
|
||
/// Human hint shown when the binary is missing.
|
||
#[serde(default)]
|
||
pub install_hint: Option<String>,
|
||
/// `host:port` of a server that speaks LSP over TCP (Godot's editor). Connects
|
||
/// there when something listens; otherwise starts `command` (when installed)
|
||
/// and connects once it accepts: to `127.0.0.1:${port}` when the arguments
|
||
/// contain `${port}` (a free port), else to this address. `${root}` in the
|
||
/// arguments is the workspace root. A server reached without starting it is
|
||
/// never shut down or killed by the IDE.
|
||
#[serde(default)]
|
||
pub connect: Option<String>,
|
||
/// An alternative to the servers listed before it (pyright after
|
||
/// basedpyright): only used while none of those is installed.
|
||
#[serde(default)]
|
||
pub fallback: bool,
|
||
/// What the server is used for (`completion`, `code-action`, `diagnostics`,
|
||
/// … `FEATURES` in `ide-lsp/src/caps.rs`); empty means everything. The other
|
||
/// capabilities it announces are ignored, so a refactoring-only server next
|
||
/// to the language's own one adds no completion, hover or formatting.
|
||
#[serde(default)]
|
||
pub features: Vec<String>,
|
||
/// Code action kinds kept from the server, matched like LSP's `only`
|
||
/// (`refactor` keeps `refactor.extract`); empty keeps every kind.
|
||
#[serde(default)]
|
||
pub code_action_kinds: Vec<String>,
|
||
/// Newest Java release the server runs on (kotlin-language-server: 21). A
|
||
/// newer `JAVA_HOME` is replaced by the newest installed JDK that fits.
|
||
#[serde(default)]
|
||
pub max_java: Option<u32>,
|
||
/// Set by the settings hook when the server can't run here (jdtls without
|
||
/// a JDK 21+); reported as the failure instead of launching it.
|
||
#[serde(skip)]
|
||
pub launch_error: Option<String>,
|
||
/// Register source files with tsserver when no project configuration exists.
|
||
#[serde(default)]
|
||
pub tsserver_project: bool,
|
||
/// Re-sends the open documents (unchanged) once `workspace/configuration` is answered:
|
||
/// the server (Intelephense) applies some settings, such as the PHP version, only when
|
||
/// it parses a document.
|
||
#[serde(default)]
|
||
pub resync_on_configuration: bool,
|
||
/// Set on plugin languages: start exactly this approved program.
|
||
#[serde(skip)]
|
||
pub pinned: Option<ProgramPin>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
#[serde(rename_all = "kebab-case")]
|
||
pub struct DebuggerConfig {
|
||
/// DAP adapter name, e.g. `codelldb`, `debugpy`, `js-debug`, `delve`.
|
||
pub adapter: String,
|
||
pub command: String,
|
||
#[serde(default)]
|
||
pub args: Vec<String>,
|
||
/// `stdio` or `tcp`.
|
||
#[serde(default = "default_transport")]
|
||
pub transport: String,
|
||
/// `tcp` only: `host:port` of an adapter running inside another program (the
|
||
/// Godot editor). Used when it accepts connections; otherwise `command` is started.
|
||
#[serde(default)]
|
||
pub connect: Option<String>,
|
||
#[serde(default)]
|
||
pub install_hint: Option<String>,
|
||
/// Set on plugin languages: start exactly this approved program.
|
||
#[serde(skip)]
|
||
pub pinned: Option<ProgramPin>,
|
||
}
|
||
|
||
/// The program a plugin's `process:` grant approved:
|
||
/// a plugin language's server or debug adapter starts from this exact path,
|
||
/// and only while the file still has the approved content. Set by the plugin
|
||
/// host, never read from a definition.
|
||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||
pub struct ProgramPin {
|
||
pub path: PathBuf,
|
||
/// blake3 of the file when approved.
|
||
pub hash: String,
|
||
}
|
||
|
||
impl ProgramPin {
|
||
/// Approves `path` as it is now.
|
||
pub fn approve(path: impl Into<PathBuf>) -> std::io::Result<Self> {
|
||
let path = path.into();
|
||
let hash = ide_extension::ledger::file_hash(&path)?;
|
||
Ok(Self { path, hash })
|
||
}
|
||
|
||
/// Checks the file right before it is started; the error tells the user what to do.
|
||
pub fn verify(&self) -> Result<&Path, String> {
|
||
match ide_extension::ledger::file_hash(&self.path) {
|
||
Ok(hash) if hash == self.hash => Ok(&self.path),
|
||
Ok(_) => Err(format!(
|
||
"{} changed since it was approved; review the plugin's permissions in Settings › Plugins",
|
||
self.path.display()
|
||
)),
|
||
Err(e) => Err(format!("{}: {e}", self.path.display())),
|
||
}
|
||
}
|
||
}
|
||
|
||
fn default_transport() -> String {
|
||
"stdio".into()
|
||
}
|
||
|
||
/// The UI-facing view of a language.
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct LanguageInfo {
|
||
pub id: LanguageId,
|
||
pub name: String,
|
||
pub extensions: Vec<String>,
|
||
pub ui_mode: Option<String>,
|
||
pub line_comment: Option<String>,
|
||
pub block_comment: Option<(String, String)>,
|
||
pub indent: Option<IndentConfig>,
|
||
/// A debug adapter is configured (breakpoints make sense in these files).
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
pub debuggable: bool,
|
||
/// Names of the configured language servers (Settings › Inspections).
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
pub language_servers: Vec<String>,
|
||
/// Exact file names and file-name patterns of the definition (Settings › File Types).
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
pub filenames: Vec<String>,
|
||
/// First-line regex of the definition (shebangs, `<?php`).
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
pub first_line: Option<String>,
|
||
/// Content of a new scratch file; `$END$` marks where the caret goes.
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
pub scratch_template: Option<String>,
|
||
}
|
||
|
||
impl From<&LanguageConfig> for LanguageInfo {
|
||
fn from(c: &LanguageConfig) -> Self {
|
||
Self {
|
||
id: c.id.clone(),
|
||
name: c.name.clone(),
|
||
extensions: c.extensions.clone(),
|
||
ui_mode: c.ui_mode.clone(),
|
||
line_comment: c.line_comment.clone(),
|
||
block_comment: c.block_comment.clone(),
|
||
indent: c.indent,
|
||
debuggable: c.debugger.is_some(),
|
||
language_servers: c.language_servers.iter().map(|s| s.name.clone()).collect(),
|
||
filenames: c.filenames.clone(),
|
||
first_line: c.first_line.clone(),
|
||
scratch_template: c.scratch_template.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum FoldKind {
|
||
Region,
|
||
Comment,
|
||
Imports,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
pub struct FoldRange {
|
||
pub start_line: u32,
|
||
pub end_line: u32,
|
||
pub kind: FoldKind,
|
||
}
|
||
|
||
/// A symbol definition extracted for the project index.
|
||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct SymbolTag {
|
||
pub name: String,
|
||
pub kind: SymbolKind,
|
||
/// Enclosing symbol, e.g. class name for a method.
|
||
pub container: Option<String>,
|
||
pub range: Range,
|
||
}
|