mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
fix(run): Windows system python, pytest templates in gutter configs, pixi env detection (#1886)
* fix(run): use system python per OS and pytest templates, detect pixi envs Gutter/context pytest configs on Windows used `python3`, which is not a command there; fall back to `python` on Windows. Gutter-created pytest configs now also inherit "Edit configuration templates... > pytest" values via a template cache populated with the run configurations. Pixi environments (`.pixi/envs/<env>`) are detected as the project's Python: as run-configuration env (`PyEnv::Pixi`, tools via `pixi run` or the env's own executables), as the project interpreter for scripts/tests and the debugger, and as selectable SDKs, so the interpreter picked in the IDE feeds completion. Fixes l0ng-ai/openidea#1885 * fix(run): drop the pytest template's own target in gutter configs testConfigFor always appends the gutter file, so a target stored on the template would run twice (template target plus the file). The gutter file is the target. Also corrects pixi_env's doc comment to the implemented name order.
This commit is contained in:
1 parent
0eca4366d0
commit
d018771a8d
8 files changed
+270
-12
No files matched your search
@@ -835,6 +835,9 @@ enum PyEnv {
|
||||
Runner(&'static str),
|
||||
/// A project-local virtualenv directory (relative to root).
|
||||
Venv(String),
|
||||
/// A pixi environment directory `.pixi/envs/<env>` (relative to root); tools
|
||||
/// also run as `pixi run …`, the interpreter directly for the debugger.
|
||||
Pixi(String),
|
||||
System,
|
||||
}
|
||||
|
||||
@@ -854,9 +857,25 @@ impl PyEnv {
|
||||
return Self::Venv(venv.to_owned());
|
||||
}
|
||||
}
|
||||
if let Some(env) = Self::pixi_env(root) {
|
||||
return Self::Pixi(env);
|
||||
}
|
||||
Self::System
|
||||
}
|
||||
|
||||
/// The first pixi environment `.pixi/envs/<env>` holding a Python, relative
|
||||
/// to the root, in name order.
|
||||
fn pixi_env(root: &Path) -> Option<String> {
|
||||
let envs = root.join(".pixi/envs");
|
||||
let mut dirs: Vec<_> =
|
||||
std::fs::read_dir(&envs).into_iter().flatten().flatten().map(|e| e.path()).filter(|d| d.is_dir()).collect();
|
||||
dirs.sort_by_key(|d| d.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default());
|
||||
dirs.iter()
|
||||
.find(|d| d.join(Self::python_exe()).is_file() || d.join("bin").join(Self::python_exe()).is_file())
|
||||
.and_then(|d| d.strip_prefix(root).ok())
|
||||
.map(|rel| rel.to_string_lossy().replace('\\', "/"))
|
||||
}
|
||||
|
||||
fn bin_dir() -> &'static str {
|
||||
if cfg!(windows) { "Scripts" } else { "bin" }
|
||||
}
|
||||
@@ -880,6 +899,18 @@ impl PyEnv {
|
||||
}
|
||||
}
|
||||
Self::System => (tool.to_owned(), Vec::new()),
|
||||
// A pixi env's executables sit in `bin` (Unix) or `Scripts` (Windows)
|
||||
// like a venv's; else `pixi run` finds the tool.
|
||||
Self::Pixi(v) => {
|
||||
let exe = if cfg!(windows) { format!("{tool}.exe") } else { tool.to_owned() };
|
||||
for bin in ["Scripts", "bin"] {
|
||||
let rel = format!("{v}/{bin}/{exe}");
|
||||
if root.join(&rel).is_file() {
|
||||
return (rel, Vec::new());
|
||||
}
|
||||
}
|
||||
("pixi".to_owned(), vec!["run".into(), tool.into()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,9 +925,19 @@ impl PyEnv {
|
||||
match self {
|
||||
Self::Runner(r) => ((*r).to_owned(), vec!["run".into(), "python".into()]),
|
||||
Self::Venv(v) => (format!("{v}/{}/{}", Self::bin_dir(), Self::python_exe()), Vec::new()),
|
||||
Self::Pixi(v) => (Self::pixi_python(v), Vec::new()),
|
||||
Self::System => ((if cfg!(windows) { "python" } else { "python3" }).to_owned(), Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// `<env>/python.exe` (Windows) or `<env>/bin/python` of a pixi environment.
|
||||
fn pixi_python(env: &str) -> String {
|
||||
if cfg!(windows) {
|
||||
format!("{env}/{}", Self::python_exe())
|
||||
} else {
|
||||
format!("{env}/bin/{}", Self::python_exe())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The interpreter an imported debugger configuration runs with, as `(command, leading args)`:
|
||||
@@ -909,6 +950,9 @@ pub(crate) fn project_python(root: &Path) -> (String, Vec<String>) {
|
||||
return (rel, Vec::new());
|
||||
}
|
||||
}
|
||||
if let Some(env) = PyEnv::pixi_env(root) {
|
||||
return (PyEnv::pixi_python(&env), Vec::new());
|
||||
}
|
||||
PyEnv::detect(root).python()
|
||||
}
|
||||
|
||||
@@ -943,8 +987,9 @@ fn uses_pytest(root: &Path) -> bool {
|
||||
fn detect_python(root: &Path, out: &mut Vec<RunConfig>) {
|
||||
let markers = ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "pytest.ini", "tox.ini", "manage.py"];
|
||||
let env = PyEnv::detect(root);
|
||||
// A project virtualenv marks a Python project too (scripts + tests without packaging files).
|
||||
let venv = matches!(env, PyEnv::Venv(_));
|
||||
// A project virtualenv or pixi environment marks a Python project too
|
||||
// (scripts + tests without packaging files).
|
||||
let venv = matches!(env, PyEnv::Venv(_) | PyEnv::Pixi(_));
|
||||
if !venv && !markers.iter().any(|m| root.join(m).is_file()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ pub fn ruff_configuration(root: &Path, configured: &str) -> IdeResult<Option<Pat
|
||||
}
|
||||
|
||||
/// The project interpreter: the configured one, else a project virtualenv
|
||||
/// (`.venv`, `venv`, …) in the project root, else the Python the project pins for mise / asdf.
|
||||
/// (`.venv`, `venv`, …) or pixi environment (`.pixi/envs/<env>`) in the project
|
||||
/// root, else the Python the project pins for mise / asdf.
|
||||
pub fn python_interpreter(root: &Path, s: &Settings) -> Option<PathBuf> {
|
||||
setting_path(root, &s.languages.python.interpreter)
|
||||
.or_else(|| project_venv_python(root))
|
||||
|
||||
@@ -73,7 +73,7 @@ fn dedupe_by_real_path(paths: Vec<(PathBuf, &'static str)>) -> Vec<(PathBuf, &'s
|
||||
/// Project-local virtualenv directories checked by auto-detection, in order.
|
||||
pub(crate) const PROJECT_VENVS: [&str; 4] = [".venv", "venv", "env", ".env"];
|
||||
|
||||
/// `<root>/<venv>/bin/python` of the first project virtualenv.
|
||||
/// `<root>/<venv>/bin/python` of the first project virtualenv or pixi environment.
|
||||
pub(crate) fn project_venv_python(root: &Path) -> Option<PathBuf> {
|
||||
PROJECT_VENVS
|
||||
.iter()
|
||||
@@ -81,6 +81,11 @@ pub(crate) fn project_venv_python(root: &Path) -> Option<PathBuf> {
|
||||
.filter(|v| v.join("pyvenv.cfg").is_file())
|
||||
.map(|v| v.join(bin_dir()).join(exe("python")))
|
||||
.find(|p| p.is_file())
|
||||
.or_else(|| {
|
||||
subdirs(&root.join(".pixi/envs")).iter().find_map(|env| {
|
||||
[env.join(exe("python")), env.join("bin").join(exe("python"))].into_iter().find(|p| p.is_file())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// `/usr/bin/python3` on macOS is a stub that opens an installer dialog when
|
||||
@@ -110,6 +115,14 @@ fn python(root: Option<&Path>) -> Vec<Sdk> {
|
||||
found.push((dir.join(bin_dir()).join(exe("python")), "venv"));
|
||||
}
|
||||
}
|
||||
// pixi environments (`.pixi/envs/<env>`), which are no virtualenvs.
|
||||
for env in subdirs(&root.join(".pixi/envs")) {
|
||||
let python =
|
||||
[env.join(exe("python")), env.join("bin").join(exe("python"))].into_iter().find(|p| p.is_file());
|
||||
if let Some(python) = python {
|
||||
found.push((python, "pixi"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(venv) = std::env::var("VIRTUAL_ENV") {
|
||||
found.push((Path::new(&venv).join(bin_dir()).join(exe("python")), "venv"));
|
||||
@@ -215,6 +228,10 @@ pub(crate) fn python_sdk(path: &Path, source: &str, root: Option<&Path>) -> Opti
|
||||
.map(|n| n.to_string_lossy().into_owned());
|
||||
format!("Python {short} (conda: {})", env.unwrap_or_default())
|
||||
}
|
||||
(None, "pixi") => {
|
||||
let env = pixi_env_name(path).unwrap_or_default();
|
||||
format!("Python {short} (pixi: {env})")
|
||||
}
|
||||
(None, "pyenv") => format!("Python {short} (pyenv)"),
|
||||
(None, tool @ ("uv" | "mise" | "asdf")) => format!("Python {short} ({tool})"),
|
||||
_ => format!("Python {short}"),
|
||||
@@ -224,11 +241,26 @@ pub(crate) fn python_sdk(path: &Path, source: &str, root: Option<&Path>) -> Opti
|
||||
name: label,
|
||||
version: Some(version),
|
||||
source: source.to_owned(),
|
||||
home: venv.or_else(|| path.parent().and_then(Path::parent).map(Path::to_path_buf)).map(Into::into),
|
||||
home: venv
|
||||
.or_else(|| (source == "pixi").then(|| pixi_env_home(path)).flatten())
|
||||
.or_else(|| path.parent().and_then(Path::parent).map(Path::to_path_buf))
|
||||
.map(Into::into),
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// `.pixi/envs/<env>` of a pixi interpreter (`<env>/python`, `<env>/bin/python`).
|
||||
fn pixi_env_home(python: &Path) -> Option<PathBuf> {
|
||||
let dir = python.parent()?;
|
||||
let env = if dir.file_name().is_some_and(|n| n == "bin") { dir.parent()? } else { dir };
|
||||
(env.parent()?.file_name()? == "envs" && env.parent()?.parent()?.file_name()? == ".pixi").then(|| env.to_path_buf())
|
||||
}
|
||||
|
||||
/// The name of a pixi interpreter's environment (`<env>` of `.pixi/envs/<env>`).
|
||||
fn pixi_env_name(python: &Path) -> Option<String> {
|
||||
pixi_env_home(python)?.file_name().map(|n| n.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
fn pyvenv_version(python: &Path) -> Option<String> {
|
||||
let cfg = std::fs::read_to_string(venv_of(python)?.join("pyvenv.cfg")).ok()?;
|
||||
cfg.lines().find_map(|l| {
|
||||
@@ -781,3 +813,34 @@ mod maven_path_tests {
|
||||
assert_eq!(home, dunce::canonicalize(real.path()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pixi_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_pixi_env_is_a_project_python_and_an_sdk_home() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let env = root.path().join(".pixi/envs/default");
|
||||
let bin = if cfg!(windows) { env.clone() } else { env.join("bin") };
|
||||
std::fs::create_dir_all(&bin).unwrap();
|
||||
let python = bin.join(exe("python"));
|
||||
std::fs::write(&python, "").unwrap();
|
||||
|
||||
assert_eq!(project_venv_python(root.path()), Some(python.clone()));
|
||||
assert_eq!(pixi_env_home(&python), Some(env.clone()));
|
||||
assert_eq!(pixi_env_name(&python).as_deref(), Some("default"));
|
||||
|
||||
// A second env sorts after; the first with a Python wins.
|
||||
let other = root.path().join(".pixi/envs/aaa-empty");
|
||||
std::fs::create_dir_all(&other).unwrap();
|
||||
assert_eq!(project_venv_python(root.path()), Some(python));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_pixi_paths_have_no_pixi_home() {
|
||||
assert_eq!(pixi_env_home(Path::new("/usr/bin/python3")), None);
|
||||
assert_eq!(pixi_env_home(Path::new("/p/.venv/bin/python")), None);
|
||||
assert_eq!(pixi_env_home(Path::new("/p/envs/x/bin/python")), None);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { importConfigs, importLabel } from "./importConfigs";
|
||||
import { targetName } from "./remote";
|
||||
import { saveEditedConfigs, selectConfig, useRun } from "./store";
|
||||
import { blankTemplate, COMPOUND, missingParams, newConfigOfType, templateId, typeFor } from "./types";
|
||||
import { cacheRunTemplates } from "./runTemplates";
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
@@ -142,7 +143,11 @@ function EditConfigsDialog() {
|
||||
if (!isTauri()) return;
|
||||
let live = true;
|
||||
call(commands.runTemplates())
|
||||
.then((t) => live && setTemplates(t))
|
||||
.then((t) => {
|
||||
if (!live) return;
|
||||
setTemplates(t);
|
||||
cacheRunTemplates(t);
|
||||
})
|
||||
.catch(() => live && setTemplates([]));
|
||||
return () => {
|
||||
live = false;
|
||||
@@ -305,7 +310,10 @@ function EditConfigsDialog() {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (dirty) await saveEditedConfigs(draft, removedDetectedIds);
|
||||
if (templatesDirty && templates) await call(commands.runSaveTemplates(templates));
|
||||
if (templatesDirty && templates) {
|
||||
await call(commands.runSaveTemplates(templates));
|
||||
cacheRunTemplates(templates);
|
||||
}
|
||||
setRemovedDetectedIds([]);
|
||||
setDirty(false);
|
||||
setTemplatesDirty(false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { usePlatform } from "@/platform/platformInfo";
|
||||
import {
|
||||
commandLine,
|
||||
duplicateConfig,
|
||||
@@ -14,6 +15,8 @@ import {
|
||||
validateConfig,
|
||||
} from "./configs";
|
||||
import { contextConfig, fileRole } from "./context";
|
||||
import { cacheRunTemplates } from "./runTemplates";
|
||||
import { registerBuiltinTypes } from "./types";
|
||||
import { envFromRows } from "./EnvDialog";
|
||||
import { configRemoved, defaultSelection, rerunConfig, useRun, type RunSession } from "./store";
|
||||
|
||||
@@ -380,6 +383,85 @@ describe("context configuration", () => {
|
||||
expect(contextConfig("tools/gen.py", plain)).toMatchObject({ command: "python3", args: ["tools/gen.py"] });
|
||||
expect(contextConfig("tools/gen.py", [])).toMatchObject({ command: "python3", args: ["tools/gen.py"] });
|
||||
});
|
||||
|
||||
describe("system python per OS (#1885)", () => {
|
||||
const os = usePlatform.getState().os;
|
||||
afterEach(() => {
|
||||
usePlatform.setState({ os });
|
||||
cacheRunTemplates([]);
|
||||
});
|
||||
|
||||
it("falls back to `python` on Windows, `python3` elsewhere", () => {
|
||||
usePlatform.setState({ os: "windows" });
|
||||
// `python3` is not a command on Windows.
|
||||
expect(contextConfig("tools/gen.py", [])).toMatchObject({ command: "python", args: ["tools/gen.py"] });
|
||||
expect(contextConfig("worker/test_worker.py", [])).toMatchObject({
|
||||
command: "python",
|
||||
args: ["-m", "pytest", "worker/test_worker.py"],
|
||||
});
|
||||
usePlatform.setState({ os: "linux" });
|
||||
expect(contextConfig("tools/gen.py", [])).toMatchObject({ command: "python3", args: ["tools/gen.py"] });
|
||||
expect(contextConfig("worker/test_worker.py", [])).toMatchObject({
|
||||
command: "python3",
|
||||
args: ["-m", "pytest", "worker/test_worker.py"],
|
||||
});
|
||||
});
|
||||
|
||||
it("seeds gutter pytest configs from the pytest template", () => {
|
||||
const dispose = registerBuiltinTypes();
|
||||
try {
|
||||
cacheRunTemplates([
|
||||
cfg({ id: "template:pytest", kind: "pytest", params: { command: "pixi", runnerArgs: "run pytest" } }),
|
||||
]);
|
||||
expect(contextConfig("worker/test_worker.py", [])).toMatchObject({
|
||||
command: "pixi",
|
||||
args: ["run", "pytest", "worker/test_worker.py"],
|
||||
isTest: true,
|
||||
});
|
||||
// Template options come along; the file stays the target.
|
||||
cacheRunTemplates([
|
||||
cfg({
|
||||
id: "template:pytest",
|
||||
kind: "pytest",
|
||||
params: { command: "uv", runnerArgs: "run pytest", options: "-x" },
|
||||
}),
|
||||
]);
|
||||
expect(contextConfig("worker/test_worker.py", [])!.args).toEqual([
|
||||
"run",
|
||||
"pytest",
|
||||
"-x",
|
||||
"worker/test_worker.py",
|
||||
]);
|
||||
// A target stored on the template itself is dropped: the gutter file is
|
||||
// the target, or the run would execute both.
|
||||
cacheRunTemplates([
|
||||
cfg({
|
||||
id: "template:pytest",
|
||||
kind: "pytest",
|
||||
params: { command: "pixi", runnerArgs: "run pytest", target: "tests", options: "-x" },
|
||||
}),
|
||||
]);
|
||||
expect(contextConfig("worker/test_worker.py", [])!.args).toEqual([
|
||||
"run",
|
||||
"pytest",
|
||||
"-x",
|
||||
"worker/test_worker.py",
|
||||
]);
|
||||
// No template: the detected interpreter / system python as before.
|
||||
cacheRunTemplates([]);
|
||||
const venv = [
|
||||
cfg({ id: "python:module:app", kind: "python", command: ".venv/bin/python", args: ["-m", "app"] }),
|
||||
];
|
||||
expect(contextConfig("worker/test_worker.py", venv)).toMatchObject({
|
||||
command: ".venv/bin/python",
|
||||
args: ["-m", "pytest", "worker/test_worker.py"],
|
||||
});
|
||||
} finally {
|
||||
dispose();
|
||||
cacheRunTemplates([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("rerunning a session after its configuration changed (#1497)", () => {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { phpunitArgs } from "./testing/phpunit";
|
||||
import { goTestParts } from "./goArguments";
|
||||
import { goBuildType } from "./goTypes";
|
||||
import { isWindows } from "./jvmTypes";
|
||||
import { typeFor } from "./types";
|
||||
import { runTemplate } from "./runTemplates";
|
||||
|
||||
export type FileRole = "test" | "main" | "script" | null;
|
||||
|
||||
@@ -135,9 +137,16 @@ export function projectPython(configs: RunConfig[], relPath?: string): { command
|
||||
return null;
|
||||
}
|
||||
|
||||
/** `python` on Windows (where `python3` is not a command), `python3` elsewhere. */
|
||||
function systemPython(): string {
|
||||
return isWindows() ? "python" : "python3";
|
||||
}
|
||||
|
||||
/**
|
||||
* PyCharm runs pytest for a `test_*.py` file even when the project declares no
|
||||
* test setup: `python -m pytest`, with the project's interpreter when detected.
|
||||
* The "Edit configuration templates…" › pytest values win over both the
|
||||
* interpreter and the runner arguments, like a template a new config inherits.
|
||||
*/
|
||||
function implicitPytest(relPath: string, configs: RunConfig[]): RunConfig | null {
|
||||
if (!relPath.endsWith(".py")) return null;
|
||||
@@ -146,11 +155,11 @@ function implicitPytest(relPath: string, configs: RunConfig[]): RunConfig | null
|
||||
relPath,
|
||||
);
|
||||
const py = projectPython(owners);
|
||||
return {
|
||||
const base: RunConfig = {
|
||||
id: "pytest:implicit",
|
||||
name: "pytest",
|
||||
kind: "pytest",
|
||||
command: py?.command ?? "python3",
|
||||
command: py?.command ?? systemPython(),
|
||||
args: [...(py?.prefix ?? []), "-m", "pytest"],
|
||||
cwd: owners[0]?.cwd ?? null,
|
||||
module: owners[0]?.module,
|
||||
@@ -159,6 +168,25 @@ function implicitPytest(relPath: string, configs: RunConfig[]): RunConfig | null
|
||||
isTest: true,
|
||||
debugAdapter: "debugpy",
|
||||
};
|
||||
return applyPytestTemplate(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Edit configuration templates…" › pytest applied to a gutter/context pytest
|
||||
* configuration: its Command and Runner arguments replace the detected
|
||||
* interpreter (`pixi` + `run pytest`, a venv's `pytest`, …) while the file the
|
||||
* context adds stays the target — a target stored on the template itself is
|
||||
* dropped, or the run would execute both it and the file. Without a template
|
||||
* the config is unchanged.
|
||||
*/
|
||||
function applyPytestTemplate(config: RunConfig): RunConfig {
|
||||
const type = typeFor("pytest");
|
||||
const template = runTemplate("pytest");
|
||||
const params = type && template ? { ...type.defaults, ...template.params, target: "" } : null;
|
||||
if (!type || !params) return config;
|
||||
const line = type.commandLine(params);
|
||||
if (!line) return config;
|
||||
return { ...config, command: line.command, args: line.args };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,7 +375,7 @@ function scriptConfigFor(
|
||||
};
|
||||
if (relPath.endsWith(".py")) {
|
||||
// Reuse the project's interpreter (venv / uv / poetry) when detected.
|
||||
const py = projectPython(configs) ?? { command: "python3", prefix: [] };
|
||||
const py = projectPython(configs) ?? { command: systemPython(), prefix: [] };
|
||||
const module = pythonModuleRun(relPath, content, configs);
|
||||
if (module)
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* A synchronous cache of the "Edit configuration templates…" values, so the
|
||||
* run-context producers (gutter icons, ⌃⇧R) — which build configurations
|
||||
* synchronously — can apply them. `loadConfigs` refreshes it from disk; the
|
||||
* Edit Configurations dialog writes through it on save.
|
||||
*/
|
||||
import { call, commands, isTauri, type RunConfig } from "@/platform/ipc";
|
||||
|
||||
const cache = new Map<string, RunConfig>();
|
||||
|
||||
/** The stored template of `kind` ("Edit configuration templates…" › kind), if any. */
|
||||
export function runTemplate(kind: string): RunConfig | undefined {
|
||||
return cache.get(kind);
|
||||
}
|
||||
|
||||
/** Caches the stored templates (called with `runTemplates()`'s result). */
|
||||
export function cacheRunTemplates(templates: readonly RunConfig[]): void {
|
||||
cache.clear();
|
||||
for (const t of templates) cache.set(t.kind, t);
|
||||
}
|
||||
|
||||
/** Refreshes the cache from disk; a no-op off the desktop. */
|
||||
export async function loadRunTemplates(): Promise<void> {
|
||||
if (!isTauri()) return;
|
||||
try {
|
||||
cacheRunTemplates(await call(commands.runTemplates()));
|
||||
} catch {
|
||||
/* keep the previous cache */
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import { compoundChildren } from "./configs";
|
||||
import { remoteLabel, startWithRemedies } from "./remote";
|
||||
import { TerminalOutput } from "./terminalOutput";
|
||||
import { typeFor } from "./types";
|
||||
import { loadRunTemplates } from "./runTemplates";
|
||||
import { recordRun } from "./services";
|
||||
import { startedPort } from "./serverPort";
|
||||
import { tomcatBrowserUrl } from "./tomcat";
|
||||
@@ -197,7 +198,7 @@ export async function loadConfigs(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const saved = await call(commands.runConfigs());
|
||||
const [saved] = await Promise.all([call(commands.runConfigs()), loadRunTemplates()]);
|
||||
if (useProject.getState().project?.root !== root) return;
|
||||
// Temporary configs survive reloads (and restarts) until saved or evicted; a saved one wins.
|
||||
// They are kept without what settings derived: today's toolchains apply to them, as to the others.
|
||||
|
||||
Reference in new issue
Block a user