Skip to content

Commit

Permalink
feat: supports vscode variables in configurations and (#53)
Browse files Browse the repository at this point in the history
+ more testing and validation
  • Loading branch information
Myriad-Dreamin authored Mar 16, 2024
1 parent c27bf6a commit d8cc783
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 16 deletions.
102 changes: 96 additions & 6 deletions crates/tinymist/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,22 +356,25 @@ impl Config {
};

// Convert the input pairs to a dictionary.
let inputs: TypstDict = command
.inputs
.iter()
.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()))
.collect();
let inputs: Option<TypstDict> = if command.inputs.is_empty() {
None
} else {
let pairs = command.inputs.iter();
let pairs = pairs.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()));
Some(pairs.collect())
};

// todo: the command.root may be not absolute
self.typst_extra_args = Some(CompileExtraOpts {
entry: command.input,
root_dir: command.root,
inputs: Some(inputs),
inputs,
font_paths: command.font_paths,
});
}
}

self.validate()?;
Ok(())
}

Expand Down Expand Up @@ -415,6 +418,24 @@ impl Config {
self.semantic_tokens_listeners.push(listener);
}

fn validate(&self) -> anyhow::Result<()> {
if let Some(root) = &self.root_path {
if !root.is_absolute() {
bail!("rootPath must be an absolute path: {root:?}");
}
}

if let Some(extra_args) = &self.typst_extra_args {
if let Some(root) = &extra_args.root_dir {
if !root.is_absolute() {
bail!("typstExtraArgs.root must be an absolute path: {root:?}");
}
}
}

Ok(())
}

// pub fn listen_formatting(&mut self, listener:
// Listener<ExperimentalFormatterMode>) { self.formatter_listeners.
// push(listener); }
Expand Down Expand Up @@ -644,3 +665,72 @@ impl Init {
(service, Ok(res))
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn test_config_update() {
let mut config = Config::default();

let root_path = if cfg!(windows) { "C:\\root" } else { "/root" };

let update = json!({
"outputPath": "out",
"exportPdf": "onSave",
"rootPath": root_path,
"semanticTokens": "enable",
"experimentalFormatterMode": "enable",
"typstExtraArgs": ["--root", root_path]
});

config.update(&update).unwrap();

assert_eq!(config.output_path, "out");
assert_eq!(config.export_pdf, ExportPdfMode::OnSave);
assert_eq!(config.root_path, Some(PathBuf::from(root_path)));
assert_eq!(config.semantic_tokens, SemanticTokensMode::Enable);
assert_eq!(config.formatter, ExperimentalFormatterMode::Enable);
assert_eq!(
config.typst_extra_args,
Some(CompileExtraOpts {
root_dir: Some(PathBuf::from(root_path)),
..Default::default()
})
);
}

#[test]
fn test_empty_extra_args() {
let mut config = Config::default();
let update = json!({
"typstExtraArgs": []
});

config.update(&update).unwrap();
}

#[test]
fn test_reject_abnormal_root() {
let mut config = Config::default();
let update = json!({
"rootPath": ".",
});

let err = format!("{}", config.update(&update).unwrap_err());
assert!(err.contains("absolute path"), "unexpected error: {}", err);
}

#[test]
fn test_reject_abnormal_root2() {
let mut config = Config::default();
let update = json!({
"typstExtraArgs": ["--root", "."]
});

let err = format!("{}", config.update(&update).unwrap_err());
assert!(err.contains("absolute path"), "unexpected error: {}", err);
}
}
4 changes: 3 additions & 1 deletion crates/tinymist/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,9 @@ impl TypstLanguageServer {
Ok(()) => {}
Err(err) => {
error!("error applying new settings: {err}");
return Err(internal_error("Internal error"));
return Err(invalid_params(format!(
"error applying new settings: {err}"
)));
}
}

Expand Down
74 changes: 67 additions & 7 deletions editors/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,21 @@ export function activate(context: ExtensionContext): Promise<void> {
}

async function startClient(context: ExtensionContext): Promise<void> {
const config = workspace.getConfiguration("tinymist");
let config: Record<string, any> = workspace.getConfiguration("tinymist");

{
const keys = Object.keys(config);
let values = keys.map((key) => config.get(key));
values = substVscodeVarsInConfig(keys, values);
config = {};
for (let i = 0; i < keys.length; i++) {
config[keys[i]] = values[i];
}
}

const serverCommand = getServer(config);
const fontPaths = vscode.workspace.getConfiguration().get<string[]>("tinymist.fontPaths");
const noSystemFonts =
vscode.workspace.getConfiguration().get<boolean | null>("tinymist.noSystemFonts") === true;
const fontPaths = config.fontPaths as string[] | null;
const noSystemFonts = config.noSystemFonts as boolean | null;
const run = {
command: serverCommand,
args: [
Expand All @@ -58,6 +68,18 @@ async function startClient(context: ExtensionContext): Promise<void> {
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "typst" }],
initializationOptions: config,
middleware: {
workspace: {
async configuration(params, token, next) {
const items = params.items.map((item) => item.section);
const result = await next(params, token);
if (!Array.isArray(result)) {
return result;
}
return substVscodeVarsInConfig(items, result);
},
},
},
};

client = new LanguageClient(
Expand Down Expand Up @@ -104,9 +126,9 @@ export function deactivate(): Promise<void> | undefined {
return client?.stop();
}

function getServer(conf: WorkspaceConfiguration): string {
const pathInConfig = conf.get<string | null>("serverPath");
if (pathInConfig !== undefined && pathInConfig !== null && pathInConfig !== "") {
function getServer(conf: Record<string, any>): string {
const pathInConfig = substVscodeVars(conf.serverPath);
if (pathInConfig) {
const validation = validateServer(pathInConfig);
if (!validation.valid) {
throw new Error(
Expand Down Expand Up @@ -333,3 +355,41 @@ async function commandRunCodeLens(...args: string[]): Promise<void> {
}
}
}

function substVscodeVars(str: string | null | undefined): string | undefined {
if (str === undefined || str === null) {
return undefined;
}
return vscodeVariables(str);
}

const STR_VARIABLES = [
"serverPath",
"tinymist.serverPath",
"rootPath",
"tinymist.rootPath",
"outputPath",
"tinymist.outputPath",
];
const STR_ARR_VARIABLES = ["fontPaths", "tinymist.fontPaths"];

// todo: documentation that, typstExtraArgs won't get variable extended
function substVscodeVarsInConfig(keys: (string | undefined)[], values: unknown[]): unknown[] {
return values.map((value, i) => {
const k = keys[i];
if (!k) {
return value;
}
if (STR_VARIABLES.includes(k)) {
return substVscodeVars(value as string);
}
if (STR_ARR_VARIABLES.includes(k)) {
const paths = value as string[];
if (!paths) {
return undefined;
}
return paths.map((path) => substVscodeVars(path));
}
return value;
});
}
2 changes: 1 addition & 1 deletion editors/vscode/src/vscode-variables.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
declare module "vscode-variables" {
export default function vscodeVariables<T>(arg: T): T;
export default function vscodeVariables<T>(arg: string, recursive?: boolean): string;
}
2 changes: 1 addition & 1 deletion tools/editor-tools/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Window {}
const acquireVsCodeApi: any;
declare const acquireVsCodeApi: any;

0 comments on commit d8cc783

Please sign in to comment.