Add scaffolding of php language server

This commit is contained in:
Piotr Osiewicz 2023-07-17 11:34:22 +02:00
parent 608c16342c
commit dc557e1647
5 changed files with 232 additions and 12 deletions

View file

@ -262,24 +262,31 @@ pub trait LspAdapter: 'static + Send + Sync {
container_dir: PathBuf, container_dir: PathBuf,
) -> Option<LanguageServerBinary>; ) -> Option<LanguageServerBinary>;
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn process_diagnostics(&self, d: &mut lsp::PublishDiagnosticsParams) {
dbg!(d);
}
async fn process_completion(&self, _: &mut lsp::CompletionItem) {} async fn process_completion(&self, d: &mut lsp::CompletionItem) {
dbg!(d);
}
async fn label_for_completion( async fn label_for_completion(
&self, &self,
_: &lsp::CompletionItem, item: &lsp::CompletionItem,
_: &Arc<Language>, _: &Arc<Language>,
) -> Option<CodeLabel> { ) -> Option<CodeLabel> {
dbg!(item);
None None
} }
async fn label_for_symbol( async fn label_for_symbol(
&self, &self,
_: &str, name: &str,
_: lsp::SymbolKind, kind: lsp::SymbolKind,
_: &Arc<Language>, _: &Arc<Language>,
) -> Option<CodeLabel> { ) -> Option<CodeLabel> {
dbg!(name);
dbg!(kind);
None None
} }
@ -321,7 +328,7 @@ pub struct CodeLabel {
pub filter_range: Range<usize>, pub filter_range: Range<usize>,
} }
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize, Debug)]
pub struct LanguageConfig { pub struct LanguageConfig {
pub name: Arc<str>, pub name: Arc<str>,
pub path_suffixes: Vec<String>, pub path_suffixes: Vec<String>,
@ -810,6 +817,7 @@ impl LanguageRegistry {
.spawn(async move { .spawn(async move {
let id = language.id; let id = language.id;
let queries = (language.get_queries)(&language.path); let queries = (language.get_queries)(&language.path);
dbg!(&language.path);
let language = let language =
Language::new(language.config, Some(language.grammar)) Language::new(language.config, Some(language.grammar))
.with_lsp_adapters(language.lsp_adapters) .with_lsp_adapters(language.lsp_adapters)
@ -819,9 +827,11 @@ impl LanguageRegistry {
Ok(language) => { Ok(language) => {
let language = Arc::new(language); let language = Arc::new(language);
let mut state = this.state.write(); let mut state = this.state.write();
state.add(language.clone()); state.add(language.clone());
state.mark_language_loaded(id); state.mark_language_loaded(id);
if let Some(mut txs) = state.loading_languages.remove(&id) { if let Some(mut txs) = state.loading_languages.remove(&id) {
dbg!(&name);
for tx in txs.drain(..) { for tx in txs.drain(..) {
let _ = tx.send(Ok(language.clone())); let _ = tx.send(Ok(language.clone()));
} }
@ -829,6 +839,7 @@ impl LanguageRegistry {
} }
Err(err) => { Err(err) => {
log::error!("failed to load language {name} - {err}"); log::error!("failed to load language {name} - {err}");
dbg!(&name);
let mut state = this.state.write(); let mut state = this.state.write();
state.mark_language_loaded(id); state.mark_language_loaded(id);
if let Some(mut txs) = state.loading_languages.remove(&id) { if let Some(mut txs) = state.loading_languages.remove(&id) {

View file

@ -151,16 +151,17 @@ impl LanguageServer {
let stdin = server.stdin.take().unwrap(); let stdin = server.stdin.take().unwrap();
let stout = server.stdout.take().unwrap(); let stout = server.stdout.take().unwrap();
let mut server = Self::new_internal( let mut server = Self::new_internal(
server_id, server_id.clone(),
stdin, stdin,
stout, stout,
Some(server), Some(server),
root_path, root_path,
code_action_kinds, code_action_kinds,
cx, cx,
|notification| { move |notification| {
log::info!( log::info!(
"unhandled notification {}:\n{}", "{} unhandled notification {}:\n{}",
server_id,
notification.method, notification.method,
serde_json::to_string_pretty( serde_json::to_string_pretty(
&notification &notification

View file

@ -2680,6 +2680,7 @@ impl Project {
key: (WorktreeId, LanguageServerName), key: (WorktreeId, LanguageServerName),
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> { ) -> Result<Option<Arc<LanguageServer>>> {
dbg!(language.name());
let setup = Self::setup_pending_language_server( let setup = Self::setup_pending_language_server(
this, this,
initialization_options, initialization_options,
@ -2694,7 +2695,6 @@ impl Project {
Some(language_server) => language_server, Some(language_server) => language_server,
None => return Ok(None), None => return Ok(None),
}; };
let this = match this.upgrade(cx) { let this = match this.upgrade(cx) {
Some(this) => this, Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")), None => return Err(anyhow!("failed to upgrade project handle")),

View file

@ -13,6 +13,7 @@ mod json;
#[cfg(feature = "plugin_runtime")] #[cfg(feature = "plugin_runtime")]
mod language_plugin; mod language_plugin;
mod lua; mod lua;
mod php;
mod python; mod python;
mod ruby; mod ruby;
mod rust; mod rust;
@ -135,9 +136,13 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
language( language(
"yaml", "yaml",
tree_sitter_yaml::language(), tree_sitter_yaml::language(),
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime))], vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
);
language(
"php",
tree_sitter_php::language(),
vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))],
); );
language("php", tree_sitter_php::language(), vec![]);
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -0,0 +1,203 @@
use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{future::BoxFuture, FutureExt};
use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use serde_json::{json, Value};
use smol::{fs, io::BufReader, stream::StreamExt};
use std::{
any::Any,
ffi::OsString,
future,
path::{Path, PathBuf},
sync::Arc,
};
use util::ResultExt;
fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
pub struct IntelephenseVersion(String);
pub struct IntelephenseLspAdapter {
node: Arc<NodeRuntime>,
}
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
#[allow(unused)]
pub fn new(node: Arc<NodeRuntime>) -> Self {
Self { node }
}
}
#[async_trait]
impl LspAdapter for IntelephenseLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("intelephense".into())
}
async fn fetch_latest_server_version(
&self,
_delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
// prerelease instead to sidestep this issue
dbg!("Strarting fetching server binary version");
Ok(Box::new(IntelephenseVersion(
self.node.npm_package_latest_version("intelephense").await?,
)) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
dbg!("Strarting fetching server binary");
let version = version.downcast::<IntelephenseVersion>().unwrap();
let server_path = container_dir.join(Self::SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages(&container_dir, [("intelephense", version.0.as_str())])
.await?;
}
dbg!("Fetched server binary");
Ok(LanguageServerBinary {
path: dbg!(self.node.binary_path().await)?,
arguments: intelephense_server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
dbg!("cached_server_binary");
get_cached_server_binary(container_dir, &self.node).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
dbg!("installation_test_binary");
get_cached_server_binary(container_dir, &self.node).await
}
async fn label_for_completion(
&self,
_item: &lsp::CompletionItem,
_language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
dbg!(_item.kind);
None
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
dbg!("init_options");
None
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &NodeRuntime,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(IntelephenseLspAdapter::SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
arguments: intelephense_server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})()
.await
.log_err()
}
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
use unindent::Unindent;
#[gpui::test]
async fn test_outline(cx: &mut TestAppContext) {
let language = crate::languages::language("php", tree_sitter_php::language(), None).await;
/*let text = r#"
function a() {
// local variables are omitted
let a1 = 1;
// all functions are included
async function a2() {}
}
// top-level variables are included
let b: C
function getB() {}
// exported variables are included
export const d = e;
"#
.unindent();*/
let text = r#"
function a() {
// local variables are omitted
$a1 = 1;
// all functions are included
function a2() {}
}
class Foo {}
"#
.unindent();
let buffer =
cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx));
let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
panic!(
"{:?}",
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>()
);
assert_eq!(
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>(),
&[
("function a()", 0),
("async function a2()", 1),
("let b", 0),
("function getB()", 0),
("const d", 0),
]
);
}
}