mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-12-23 08:47:50 +00:00
feat: run preview server in background (#1233)
* feat: run preview server in background * feat: pass configuration * feat: implement it * feat: touch docs and finish details
This commit is contained in:
parent
d76494380b
commit
334cb2ba1c
8 changed files with 293 additions and 139 deletions
|
|
@ -26,6 +26,8 @@ pub struct PreviewTab {
|
|||
pub compile_handler: Arc<PreviewProjectHandler>,
|
||||
/// Whether this tab is primary
|
||||
pub is_primary: bool,
|
||||
/// Whether this tab is background
|
||||
pub is_background: bool,
|
||||
}
|
||||
|
||||
pub enum PreviewRequest {
|
||||
|
|
@ -51,6 +53,15 @@ impl PreviewActor {
|
|||
}
|
||||
PreviewRequest::Kill(task_id, tx) => {
|
||||
log::info!("PreviewTask({task_id}): killing");
|
||||
|
||||
if self.tabs.get(&task_id).is_some_and(|tab| tab.is_background) {
|
||||
// todo: eliminate this warning in log in future
|
||||
log::warn!("PreviewTask({task_id}): cannot kill a background preview");
|
||||
|
||||
let _ = tx.send(Ok(JsonValue::Null));
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(mut tab) = self.tabs.remove(&task_id) else {
|
||||
let _ = tx.send(Err(internal_error("task not found")));
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -283,91 +283,20 @@ impl ServerState {
|
|||
#[cfg(feature = "preview")]
|
||||
pub fn start_preview(
|
||||
&mut self,
|
||||
args: Vec<JsonValue>,
|
||||
mut args: Vec<JsonValue>,
|
||||
) -> SchedulableResponse<crate::tool::preview::StartPreviewResponse> {
|
||||
self.start_preview_inner(args, false)
|
||||
let cli_args = get_arg_or_default!(args[0] as Vec<String>);
|
||||
self.start_preview_inner(cli_args, crate::tool::preview::PreviewKind::Regular)
|
||||
}
|
||||
|
||||
/// Start a preview instance for browsing.
|
||||
#[cfg(feature = "preview")]
|
||||
pub fn browse_preview(
|
||||
&mut self,
|
||||
args: Vec<JsonValue>,
|
||||
) -> SchedulableResponse<crate::tool::preview::StartPreviewResponse> {
|
||||
self.start_preview_inner(args, true)
|
||||
}
|
||||
|
||||
/// Start a preview instance.
|
||||
#[cfg(feature = "preview")]
|
||||
pub fn start_preview_inner(
|
||||
&mut self,
|
||||
mut args: Vec<JsonValue>,
|
||||
browsing_preview: bool,
|
||||
) -> SchedulableResponse<crate::tool::preview::StartPreviewResponse> {
|
||||
use std::path::Path;
|
||||
|
||||
use crate::tool::preview::PreviewCliArgs;
|
||||
use clap::Parser;
|
||||
|
||||
let cli_args = get_arg_or_default!(args[0] as Vec<String>);
|
||||
// clap parse
|
||||
let cli_args = ["preview"]
|
||||
.into_iter()
|
||||
.chain(cli_args.iter().map(|e| e.as_str()));
|
||||
let cli_args =
|
||||
PreviewCliArgs::try_parse_from(cli_args).map_err(|e| invalid_params(e.to_string()))?;
|
||||
|
||||
// todo: preview specific arguments are not used
|
||||
let entry = cli_args.compile.input.as_ref();
|
||||
let entry = entry
|
||||
.map(|input| {
|
||||
let input = Path::new(&input);
|
||||
if !input.is_absolute() {
|
||||
// std::env::current_dir().unwrap().join(input)
|
||||
return Err(invalid_params("entry file must be absolute path"));
|
||||
};
|
||||
|
||||
Ok(input.into())
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let task_id = cli_args.preview.task_id.clone();
|
||||
if task_id == "primary" {
|
||||
return Err(invalid_params("task id 'primary' is reserved"));
|
||||
}
|
||||
|
||||
let previewer = typst_preview::PreviewBuilder::new(cli_args.preview.clone());
|
||||
let watcher = previewer.compile_watcher();
|
||||
|
||||
let primary = &mut self.project.compiler.primary;
|
||||
// todo: recover pin status reliably
|
||||
if !cli_args.not_as_primary
|
||||
&& (browsing_preview || entry.is_some())
|
||||
&& self.preview.watchers.register(&primary.id, watcher)
|
||||
{
|
||||
let id = primary.id.clone();
|
||||
|
||||
if let Some(entry) = entry {
|
||||
self.change_main_file(Some(entry)).map_err(internal_error)?;
|
||||
}
|
||||
self.set_pin_by_preview(true, browsing_preview);
|
||||
|
||||
self.preview.start(cli_args, previewer, id, true)
|
||||
} else if let Some(entry) = entry {
|
||||
let id = self
|
||||
.restart_dedicate(&task_id, Some(entry))
|
||||
.map_err(internal_error)?;
|
||||
|
||||
if !self.project.preview.register(&id, watcher) {
|
||||
return Err(invalid_params(
|
||||
"cannot register preview to the compiler instance",
|
||||
));
|
||||
}
|
||||
|
||||
self.preview.start(cli_args, previewer, id, false)
|
||||
} else {
|
||||
return Err(internal_error("entry file must be provided"));
|
||||
}
|
||||
self.start_preview_inner(cli_args, crate::tool::preview::PreviewKind::Browsing)
|
||||
}
|
||||
|
||||
/// Kill a preview instance.
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ const CONFIG_ITEMS: &[&str] = &[
|
|||
"outputPath",
|
||||
"exportPdf",
|
||||
"rootPath",
|
||||
"preview",
|
||||
"semanticTokens",
|
||||
"formatterMode",
|
||||
"formatterPrintWidth",
|
||||
|
|
@ -263,7 +264,7 @@ const CONFIG_ITEMS: &[&str] = &[
|
|||
///
|
||||
/// Note: `Config::default` is intentionally to be "pure" and not to be
|
||||
/// affected by system environment variables.
|
||||
/// To get the configuration with system defaults, use [`Config::new`] intead.
|
||||
/// To get the configuration with system defaults, use [`Config::new`] instead.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
/// The resolution kind of the project.
|
||||
|
|
@ -287,6 +288,8 @@ pub struct Config {
|
|||
pub export_target: ExportTarget,
|
||||
/// Tinymist's completion features.
|
||||
pub completion: CompletionFeat,
|
||||
/// Tinymist's preview features.
|
||||
pub preview: PreviewFeat,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
@ -432,6 +435,9 @@ impl Config {
|
|||
assign_config!(completion.trigger_suggest := "triggerSuggest"?: bool);
|
||||
assign_config!(completion.trigger_parameter_hints := "triggerParameterHints"?: bool);
|
||||
assign_config!(completion.trigger_suggest_and_parameter_hints := "triggerSuggestAndParameterHints"?: bool);
|
||||
|
||||
assign_config!(preview := "preview"?: PreviewFeat);
|
||||
|
||||
self.compile.update_by_map(update)?;
|
||||
self.compile.validate()
|
||||
}
|
||||
|
|
@ -858,6 +864,24 @@ pub(crate) fn get_semantic_tokens_options() -> SemanticTokensOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// The preview features.
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PreviewFeat {
|
||||
/// Whether to run the preview in the background.
|
||||
pub background: BackgroundPreviewOpts,
|
||||
}
|
||||
|
||||
/// Options for background preview.
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BackgroundPreviewOpts {
|
||||
/// Whether to run the preview in the background.
|
||||
pub enabled: bool,
|
||||
/// The arguments for the background preview.
|
||||
pub args: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Additional options for compilation.
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct CompileExtraOpts {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ impl ServerState {
|
|||
.reload_projects()
|
||||
.log_error("could not restart primary");
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
service.background_preview();
|
||||
|
||||
// Run the cluster in the background after we referencing it
|
||||
client.handle.spawn(editor_actor.run());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,134 @@ use project::{watch_deps, ProjectPreviewState};
|
|||
|
||||
pub use typst_preview::CompileStatus;
|
||||
|
||||
pub enum PreviewKind {
|
||||
Regular,
|
||||
Browsing,
|
||||
Background,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
pub fn background_preview(&mut self) {
|
||||
if !self.config.preview.background.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let args = self.config.preview.background.args.clone();
|
||||
let args = args.unwrap_or_else(|| {
|
||||
vec![
|
||||
"--data-plane-host=127.0.0.1:23635".to_string(),
|
||||
"--invert-colors=auto".to_string(),
|
||||
]
|
||||
});
|
||||
|
||||
let res = self.start_preview_inner(args, PreviewKind::Background);
|
||||
|
||||
// todo: looks ugly
|
||||
self.client.handle.spawn(async move {
|
||||
let fut = match res {
|
||||
Ok(fut) => fut,
|
||||
Err(e) => {
|
||||
log::error!("failed to start background preview: {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
tokio::pin!(fut);
|
||||
let () = fut.as_mut().await;
|
||||
|
||||
if let Some(Err(e)) = fut.as_mut().take_output() {
|
||||
log::error!("failed to start background preview: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Start a preview instance.
|
||||
pub fn start_preview_inner(
|
||||
&mut self,
|
||||
cli_args: Vec<String>,
|
||||
kind: PreviewKind,
|
||||
) -> SchedulableResponse<crate::tool::preview::StartPreviewResponse> {
|
||||
use std::path::Path;
|
||||
|
||||
use crate::tool::preview::PreviewCliArgs;
|
||||
use clap::Parser;
|
||||
|
||||
// clap parse
|
||||
let cli_args = ["preview"]
|
||||
.into_iter()
|
||||
.chain(cli_args.iter().map(|e| e.as_str()));
|
||||
let cli_args =
|
||||
PreviewCliArgs::try_parse_from(cli_args).map_err(|e| invalid_params(e.to_string()))?;
|
||||
|
||||
// todo: preview specific arguments are not used
|
||||
let entry = cli_args.compile.input.as_ref();
|
||||
let entry = entry
|
||||
.map(|input| {
|
||||
let input = Path::new(&input);
|
||||
if !input.is_absolute() {
|
||||
// std::env::current_dir().unwrap().join(input)
|
||||
return Err(invalid_params("entry file must be absolute path"));
|
||||
};
|
||||
|
||||
Ok(input.into())
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let task_id = cli_args.preview.task_id.clone();
|
||||
if task_id == "primary" {
|
||||
return Err(invalid_params("task id 'primary' is reserved"));
|
||||
}
|
||||
|
||||
if cli_args.not_as_primary && matches!(kind, PreviewKind::Background) {
|
||||
return Err(invalid_params(
|
||||
"cannot start background preview as non-primary",
|
||||
));
|
||||
}
|
||||
|
||||
let previewer = typst_preview::PreviewBuilder::new(cli_args.preview.clone());
|
||||
let watcher = previewer.compile_watcher();
|
||||
|
||||
let primary = &mut self.project.compiler.primary;
|
||||
// todo: recover pin status reliably
|
||||
let is_browsing = matches!(kind, PreviewKind::Browsing | PreviewKind::Background);
|
||||
let is_background = matches!(kind, PreviewKind::Background);
|
||||
|
||||
let registered_as_primary = !cli_args.not_as_primary
|
||||
&& (is_browsing || entry.is_some())
|
||||
&& self.preview.watchers.register(&primary.id, watcher);
|
||||
if matches!(kind, PreviewKind::Background) && !registered_as_primary {
|
||||
return Err(invalid_params(
|
||||
"failed to register background preview to the primary instance",
|
||||
));
|
||||
}
|
||||
|
||||
if registered_as_primary {
|
||||
let id = primary.id.clone();
|
||||
|
||||
if let Some(entry) = entry {
|
||||
self.change_main_file(Some(entry)).map_err(internal_error)?;
|
||||
}
|
||||
self.set_pin_by_preview(true, is_browsing);
|
||||
|
||||
self.preview
|
||||
.start(cli_args, previewer, id, true, is_background)
|
||||
} else if let Some(entry) = entry {
|
||||
let id = self
|
||||
.restart_dedicate(&task_id, Some(entry))
|
||||
.map_err(internal_error)?;
|
||||
|
||||
if !self.project.preview.register(&id, watcher) {
|
||||
return Err(invalid_params(
|
||||
"cannot register preview to the compiler instance",
|
||||
));
|
||||
}
|
||||
|
||||
self.preview
|
||||
.start(cli_args, previewer, id, false, is_background)
|
||||
} else {
|
||||
return Err(internal_error("entry file must be provided"));
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The preview's view of the compiled artifact.
|
||||
pub struct PreviewCompileView {
|
||||
/// The artifact and snap.
|
||||
|
|
@ -192,7 +320,7 @@ pub struct PreviewCliArgs {
|
|||
)]
|
||||
pub control_plane_host: String,
|
||||
|
||||
/// (File) Host for the preview server. Note: if it equals to
|
||||
/// (Deprecated) (File) Host for the preview server. Note: if it equals to
|
||||
/// `data_plane_host`, same address will be used.
|
||||
#[clap(
|
||||
long = "host",
|
||||
|
|
@ -339,6 +467,7 @@ impl PreviewState {
|
|||
// compile_handler: Arc<CompileHandler>,
|
||||
project_id: ProjectInsId,
|
||||
is_primary: bool,
|
||||
is_background: bool,
|
||||
) -> SchedulableResponse<StartPreviewResponse> {
|
||||
let compile_handler = Arc::new(PreviewProjectHandler {
|
||||
project_id,
|
||||
|
|
@ -431,6 +560,7 @@ impl PreviewState {
|
|||
ctl_tx,
|
||||
compile_handler,
|
||||
is_primary,
|
||||
is_background,
|
||||
}));
|
||||
sent.map_err(|_| internal_error("failed to register preview tab"))?;
|
||||
|
||||
|
|
|
|||
|
|
@ -142,3 +142,16 @@ Whether to enable right-variant UFCS-style completion. For example, `[A].table|`
|
|||
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `true`
|
||||
|
||||
## `preview.background.enabled`
|
||||
|
||||
This configuration is only used for the editors that doesn't support lsp well, e.g. helix and zed. When it is enabled, the preview server listens a specific tcp port in the background. You can discover the background previewers in the preview panel.
|
||||
|
||||
- **Type**: `boolean`
|
||||
|
||||
## `preview.background.args`
|
||||
|
||||
The arguments that the background preview server used for. It is only used when `tinymist.preview.background` is enabled. Check `tinymist preview` to see the allowed arguments.
|
||||
|
||||
- **Type**: `array`
|
||||
- **Default**: `["--data-plane-host=127.0.0.1:23635","--invert-colors=auto"]`
|
||||
|
|
|
|||
22
editors/vscode/package.other.json
Normal file
22
editors/vscode/package.other.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"$schema": "vscode://schemas/vscode-extensions",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"properties": {
|
||||
"tinymist.preview.background.enabled": {
|
||||
"description": "This configuration is only used for the editors that doesn't support lsp well, e.g. helix and zed. When it is enabled, the preview server listens a specific tcp port in the background. You can discover the background previewers in the preview panel.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"tinymist.preview.background.args": {
|
||||
"description": "The arguments that the background preview server used for. It is only used when `tinymist.preview.background` is enabled. Check `tinymist preview` to see the allowed arguments.",
|
||||
"type": "array",
|
||||
"default": ["--data-plane-host=127.0.0.1:23635", "--invert-colors=auto"],
|
||||
"properties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,15 @@ const fs = require("fs");
|
|||
const path = require("path");
|
||||
|
||||
const projectRoot = path.join(__dirname, "../../..");
|
||||
const packageJsonPath = path.join(projectRoot, "editors/vscode/package.json");
|
||||
|
||||
const packageJsonPath = path.join(projectRoot, "editors/vscode/package.json");
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||
|
||||
const otherPackageJsonPath = path.join(projectRoot, "editors/vscode/package.other.json");
|
||||
const otherPackageJson = JSON.parse(fs.readFileSync(otherPackageJsonPath, "utf8"));
|
||||
|
||||
const config = packageJson.contributes.configuration.properties;
|
||||
const otherConfig = otherPackageJson.contributes.configuration.properties;
|
||||
|
||||
// Generate Configuration.md string
|
||||
|
||||
|
|
@ -39,85 +43,103 @@ const describeType = (typeOrTypeArray) => {
|
|||
};
|
||||
|
||||
const matchRegion = (content, regionName) => {
|
||||
const reg = new RegExp(`// region ${regionName}([\\s\\S]*?)// endregion ${regionName}`, "gm");
|
||||
const match = reg.exec(content);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to match region ${regionName}`);
|
||||
}
|
||||
return match[1];
|
||||
const reg = new RegExp(`// region ${regionName}([\\s\\S]*?)// endregion ${regionName}`, "gm");
|
||||
const match = reg.exec(content);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to match region ${regionName}`);
|
||||
}
|
||||
return match[1];
|
||||
};
|
||||
|
||||
const serverSideKeys = (() => {
|
||||
const initPath = path.join(projectRoot, "crates/tinymist/src/init.rs");
|
||||
const initContent = fs.readFileSync(initPath, "utf8");
|
||||
const configItemContent = matchRegion(initContent, "Configuration Items");
|
||||
const strReg = /"([^"]+)"/g;
|
||||
const strings = [];
|
||||
let strMatch;
|
||||
while ((strMatch = strReg.exec(configItemContent)) !== null) {
|
||||
strings.push(strMatch[1]);
|
||||
}
|
||||
return strings.map((x) => `tinymist.${x}`);
|
||||
const initPath = path.join(projectRoot, "crates/tinymist/src/init.rs");
|
||||
const initContent = fs.readFileSync(initPath, "utf8");
|
||||
const configItemContent = matchRegion(initContent, "Configuration Items");
|
||||
const strReg = /"([^"]+)"/g;
|
||||
const strings = [];
|
||||
let strMatch;
|
||||
while ((strMatch = strReg.exec(configItemContent)) !== null) {
|
||||
strings.push(strMatch[1]);
|
||||
}
|
||||
return strings.map((x) => `tinymist.${x}`);
|
||||
})();
|
||||
const isServerSideConfig = (key) => serverSideKeys.includes(key) || serverSideKeys
|
||||
.some((serverSideKey) => key.startsWith(`${serverSideKey}.`));
|
||||
const configMd = (editor, prefix) =>
|
||||
Object.keys(config)
|
||||
.map((key) => {
|
||||
const {
|
||||
description: rawDescription,
|
||||
markdownDescription,
|
||||
default: dv,
|
||||
type: itemType,
|
||||
enum: enumBase,
|
||||
enumDescriptions: enumBaseDescription,
|
||||
markdownDeprecationMessage,
|
||||
} = config[key];
|
||||
const isServerSideConfig = (key, isOther) => {
|
||||
if (
|
||||
!(
|
||||
serverSideKeys.includes(key) ||
|
||||
serverSideKeys.some((serverSideKey) => key.startsWith(`${serverSideKey}.`))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const description = markdownDescription || rawDescription;
|
||||
if (key.startsWith("tinymist.preview") && !isOther) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (markdownDeprecationMessage) {
|
||||
return true;
|
||||
};
|
||||
const configMd = (editor, prefix) => {
|
||||
const handleOne = (config, key, isOther) => {
|
||||
const {
|
||||
description: rawDescription,
|
||||
markdownDescription,
|
||||
default: dv,
|
||||
type: itemType,
|
||||
enum: enumBase,
|
||||
enumDescriptions: enumBaseDescription,
|
||||
markdownDeprecationMessage,
|
||||
} = config[key];
|
||||
|
||||
const description = markdownDescription || rawDescription;
|
||||
|
||||
if (markdownDeprecationMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
let defaultValue = dv;
|
||||
if (editor !== "vscode") {
|
||||
if (key === "tinymist.compileStatus") {
|
||||
defaultValue = "disable";
|
||||
}
|
||||
|
||||
if (!isServerSideConfig(key, isOther)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let defaultValue = dv;
|
||||
if (editor !== "vscode") {
|
||||
if (key === "tinymist.compileStatus") {
|
||||
defaultValue = "disable";
|
||||
}
|
||||
|
||||
if (!isServerSideConfig(key)) {
|
||||
return;
|
||||
const keyWithoutPrefix = key.replace("tinymist.", "");
|
||||
const name = prefix ? `tinymist.${keyWithoutPrefix}` : keyWithoutPrefix;
|
||||
const typeSection = itemType ? `\n- **Type**: ${describeType(itemType)}` : "";
|
||||
const defaultSection = defaultValue
|
||||
? `\n- **Default**: \`${JSON.stringify(defaultValue)}\``
|
||||
: "";
|
||||
const enumSections = [];
|
||||
if (enumBase) {
|
||||
// zip enum values and descriptions
|
||||
for (let i = 0; i < enumBase.length; i++) {
|
||||
if (enumBaseDescription?.[i]) {
|
||||
enumSections.push(` - \`${enumBase[i]}\`: ${enumBaseDescription[i]}`);
|
||||
} else {
|
||||
enumSections.push(` - \`${enumBase[i]}\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
const enumSection = enumSections.length ? `\n- **Enum**:\n${enumSections.join("\n")}` : "";
|
||||
|
||||
const keyWithoutPrefix = key.replace("tinymist.", "");
|
||||
const name = prefix ? `tinymist.${keyWithoutPrefix}` : keyWithoutPrefix;
|
||||
const typeSection = itemType ? `\n- **Type**: ${describeType(itemType)}` : "";
|
||||
const defaultSection = defaultValue
|
||||
? `\n- **Default**: \`${JSON.stringify(defaultValue)}\``
|
||||
: "";
|
||||
const enumSections = [];
|
||||
if (enumBase) {
|
||||
// zip enum values and descriptions
|
||||
for (let i = 0; i < enumBase.length; i++) {
|
||||
if (enumBaseDescription?.[i]) {
|
||||
enumSections.push(` - \`${enumBase[i]}\`: ${enumBaseDescription[i]}`);
|
||||
} else {
|
||||
enumSections.push(` - \`${enumBase[i]}\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
const enumSection = enumSections.length ? `\n- **Enum**:\n${enumSections.join("\n")}` : "";
|
||||
|
||||
return `## \`${name}\`
|
||||
return `## \`${name}\`
|
||||
|
||||
${description}
|
||||
${typeSection}${enumSection}${defaultSection}
|
||||
`;
|
||||
})
|
||||
};
|
||||
|
||||
const vscodeConfigs = Object.keys(config).map((key) => handleOne(config, key, false));
|
||||
const otherConfigs = Object.keys(otherConfig).map((key) => handleOne(otherConfig, key, true));
|
||||
return [...vscodeConfigs, ...(editor === "vscode" ? [] : otherConfigs)]
|
||||
.filter((x) => x)
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const configMdPath = path.join(__dirname, "..", "Configuration.md");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue