Skip to content

Commit

Permalink
feat: tsconfig command
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Nov 20, 2023
1 parent 1584003 commit 1445da7
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 51 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pkg-fmt = "zip"
[dependencies]
env_logger = "0.10.0"
ropey = "1.5.0"
serde_json = "1.0.78"
serde_json = "1.0.108"
tokio = { version = "1.17.0", features = ["full"] }
tower-lsp = { version = "0.20.0", features = ["proposed"] }
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -60,6 +60,7 @@ phf = { version = "0.11.2", features = ["macros"] }
ts-macros = { version = "0.1.0", path = "crates/ts-macros" }
bitflags = "2.4.1"
tree-sitter-javascript = "0.20.1"
pathdiff = "0.2.1"


[dev-dependencies]
Expand Down
29 changes: 27 additions & 2 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { mkdir, rm } from "node:fs/promises";
import { ObjectEncodingOptions, existsSync } from "node:fs";
import { exec, spawn, ExecOptions } from "node:child_process";
import { workspace, window, ExtensionContext, ExtensionMode } from "vscode";
import { workspace, window, ExtensionContext, commands, WorkspaceFolder } from "vscode";

import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node";

Expand Down Expand Up @@ -180,7 +180,7 @@ async function openLink(url: string) {
}

export async function activate(context: ExtensionContext) {
const traceOutputChannel = window.createOutputChannel("Odoo LSP");
const traceOutputChannel = window.createOutputChannel("Odoo LSP Extension");
let command = process.env.SERVER_PATH || "odoo-lsp";
if (!(await which(command))) {
command = (await downloadLspBinary(context)) || command;
Expand Down Expand Up @@ -216,6 +216,31 @@ export async function activate(context: ExtensionContext) {
traceOutputChannel,
};

context.subscriptions.push(commands.registerCommand('odoo-lsp.tsconfig', async () => {
const activeWindow = window.activeTextEditor?.document.uri.fsPath;
let folder: WorkspaceFolder | undefined;
if (activeWindow) {
folder = workspace.workspaceFolders?.find(ws => activeWindow.includes(ws.uri.fsPath))
}

if (!folder) folder = await window.showWorkspaceFolderPick();
if (!folder) return;

const selection = await window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: true,
title: 'Select addons roots',
defaultUri: folder.uri,
}) ?? [];

const paths = selection.map(sel => `--addons-path ${sel.fsPath}`).join(' ');
let {stdout} = await execAsync(`${command} tsconfig ${paths}`, { cwd: folder.uri.fsPath });

const doc = await workspace.openTextDocument({ language: 'json', content: stdout as string });
await window.showTextDocument(doc);
}));

client = new LanguageClient("odoo-lsp", "Odoo LSP", serverOptions, clientOptions);
await client.start();
traceOutputChannel.appendLine("Odoo LSP started");
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
"publisher": "Desdaemon",
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "odoo-lsp.tsconfig",
"title": "odoo-lsp: Generate TypeScript config"
}
],
"configuration": {
"type": "object",
"title": "odoo-lsp",
Expand Down
13 changes: 7 additions & 6 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub enum Text {
Delta(Vec<TextDocumentContentChangeEvent>),
}

#[derive(Debug)]
pub enum Language {
Python,
Xml,
Expand All @@ -78,13 +79,13 @@ impl Backend {
}
(Some((_, "xml")), _) | (_, Some(Language::Xml)) => {
self.on_change_xml(&params.text, &params.uri, rope, &mut diagnostics)
.await;
.await?;
}
(Some((_, "js")), _) | (_, Some(Language::Javascript)) => {
self.on_change_js(&params.text, &params.uri, rope, params.old_rope)
.await?;
}
_ => {}
other => return Err(diagnostic!("Unhandled language: {other:?}")).into_diagnostic(),
}
self.client
.publish_diagnostics(params.uri, diagnostics, Some(params.version))
Expand Down Expand Up @@ -333,7 +334,7 @@ impl Backend {
if !value.contains('.') {
'unscoped: {
if let Some(module) = self.index.module_of_path(Path::new(uri.path())) {
value = format!("{}.{value}", interner().resolve(module.key())).into();
value = format!("{}.{value}", interner().resolve(&module)).into();
break 'unscoped;
}
debug!(
Expand Down Expand Up @@ -433,13 +434,13 @@ impl Backend {
pub fn record_references(
&self,
inherit_id: &str,
current_module: Option<&ModuleName>,
current_module: Option<ModuleName>,
) -> miette::Result<Option<Vec<Location>>> {
let interner = &interner();
let inherit_id = if inherit_id.contains('.') {
Cow::from(inherit_id)
} else if let Some(current_module) = current_module {
Cow::from(format!("{}.{}", interner.resolve(current_module), inherit_id))
Cow::from(format!("{}.{}", interner.resolve(&current_module), inherit_id))
} else {
debug!("No current module to resolve the XML ID {inherit_id}");
return Ok(None);
Expand Down Expand Up @@ -501,7 +502,7 @@ impl Backend {
let Some(module) = self.index.module_of_path(Path::new(interner().resolve(&loc.path))) else {
return Some(None);
};
if mods.insert(*module) {
if mods.insert(module) {
Some(Some(loc))
} else {
Some(None)
Expand Down
195 changes: 195 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::{env::current_dir, fs::canonicalize, io::stdout, path::Path, process::exit};

use miette::{diagnostic, IntoDiagnostic};
use odoo_lsp::index::{interner, Index};
use serde_json::Value;

pub enum Command<'a> {
Run,
TsConfig {
addons_path: Vec<&'a str>,
output: Option<&'a Path>,
},
}

const HELP: &str = "Language server for Odoo Python/JS/XML
USAGE:
odoo-lsp
odoo-lsp tsconfig --addons-path ..
OPTIONS:
-h, --help
Display this help message
-o, --out
Specify the path to store the output.
--addons-path PATH
[tsconfig only] Specifies the roots of the addons.
Can be specified multiple times, or as a comma-separated list of paths.
";

pub fn parse_args<'r>(mut args: &'r [&'r str]) -> Command<'r> {
let mut out = Command::Run;
loop {
match args {
["tsconfig", rest @ ..] => {
args = rest;
out = Command::TsConfig {
addons_path: Vec::new(),
output: None,
};
}
["-h" | "--help", ..] => {
eprintln!("{HELP}");
exit(0);
}
["--addons-path", path, rest @ ..] => {
args = rest;
if let Command::TsConfig { addons_path, .. } = &mut out {
addons_path.extend(path.split(","));
} else {
eprintln!("--addons-path is only supported by tsconfig.");
exit(1);
}
}
["-o" | "--out", path, rest @ ..] => {
args = rest;
if let Command::TsConfig { output, .. } = &mut out {
*output = Some(Path::new(path));
}
}
[] => break,
_ => {
eprintln!("{HELP}");
exit(1);
}
}
}

out
}

pub async fn tsconfig(addons_path: &[&str], output: Option<&Path>) -> miette::Result<()> {
let addons_path = addons_path
.iter()
.map(|path| canonicalize(path).into_diagnostic())
.collect::<miette::Result<Vec<_>>>()?;

let index = Index::default();

for addons in &addons_path {
let path = addons.to_string_lossy();
index.add_root(&path, None, true).await?;
}

let mut ts_paths = serde_json::Map::new();
let pwd = current_dir().into_diagnostic()?;

let mut includes = vec![];
let mut type_roots = vec![];
let interner = interner();
for entry in &index.roots {
let root = pathdiff::diff_paths(Path::new(entry.key().as_str()), &pwd)
.ok_or_else(|| diagnostic!("Cannot diff {} to pwd", entry.key()))?;

includes.push(Value::String(root.join("**/static/src").to_string_lossy().into_owned()));
type_roots.push(Value::String(
root.join("web/tooling/types").to_string_lossy().into_owned(),
));
for module in entry.value().keys() {
let module = interner.resolve(&module);
ts_paths
.entry(format!("@{module}/*"))
.or_insert_with(|| Value::Array(vec![]))
.as_array_mut()
.unwrap()
.push(
root.join(format!("{module}/static/src/*"))
.to_string_lossy()
.into_owned()
.into(),
)
}
}

let tsconfig = serde_json::json! {{
"include": includes,
"compilerOptions": {
"baseUrl": ".",
"target": "es2019",
"checkJs": true,
"allowJs": true,
"noEmit": true,
"typeRoots": type_roots,
"paths": ts_paths,
},
"exclude": [
"/**/*.po",
"/**/*.py",
"/**/*.pyc",
"/**/*.xml",
"/**/*.png",
"/**/*.md",
"/**/*.dat",
"/**/*.scss",
"/**/*.jpg",
"/**/*.svg",
"/**/*.pot",
"/**/*.csv",
"/**/*.mo",
"/**/*.txt",
"/**/*.less",
"/**/*.bcmap",
"/**/*.properties",
"/**/*.html",
"/**/*.ttf",
"/**/*.rst",
"/**/*.css",
"/**/*.pack",
"/**/*.idx",
"/**/*.h",
"/**/*.map",
"/**/*.gif",
"/**/*.sample",
"/**/*.doctree",
"/**/*.so",
"/**/*.pdf",
"/**/*.xslt",
"/**/*.conf",
"/**/*.woff",
"/**/*.xsd",
"/**/*.eot",
"/**/*.jst",
"/**/*.flow",
"/**/*.sh",
"/**/*.yml",
"/**/*.pfb",
"/**/*.jpeg",
"/**/*.crt",
"/**/*.template",
"/**/*.pxd",
"/**/*.dylib",
"/**/*.pem",
"/**/*.rng",
"/**/*.xsl",
"/**/*.xls",
"/**/*.cfg",
"/**/*.pyi",
"/**/*.pth",
"/**/*.markdown",
"/**/*.key",
"/**/*.ico"
]
}};

if let Some(output) = output {
let file = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(output)
.into_diagnostic()?;
serde_json::to_writer_pretty(file, &tsconfig).into_diagnostic()
} else {
serde_json::to_writer_pretty(stdout(), &tsconfig).into_diagnostic()
}
}
Loading

0 comments on commit 1445da7

Please sign in to comment.