fix project list showing projects of double clicked files (#2441)
* make sure there is at least one kcl file in the dir to show in list Signed-off-by: Jess Frazelle <github@jessfraz.com> * open the correct file not assuming main.kcl Signed-off-by: Jess Frazelle <github@jessfraz.com> * add file path tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * update settings paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * new images Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
49
src-tauri/Cargo.lock
generated
@ -212,6 +212,15 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
@ -1207,6 +1216,17 @@ dependencies = [
|
|||||||
"syn 2.0.65",
|
"syn 2.0.65",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.65",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
@ -1258,6 +1278,17 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.65",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlib"
|
name = "dlib"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -2579,7 +2610,7 @@ dependencies = [
|
|||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winnow 0.5.40",
|
"winnow 0.5.40",
|
||||||
"zip",
|
"zip 1.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5471,7 +5502,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
"zip",
|
"zip 0.6.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7123,6 +7154,20 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"displaydoc",
|
||||||
|
"indexmap 2.2.6",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.11.2+zstd.1.5.2"
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
@ -121,11 +121,8 @@ async fn write_app_settings_file(app: tauri::AppHandle, configuration: Configura
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_project_settings_file_path(
|
async fn get_project_settings_file_path(project_path: &str) -> Result<PathBuf, InvokeError> {
|
||||||
app_settings: Configuration,
|
let project_dir = std::path::Path::new(project_path);
|
||||||
project_name: &str,
|
|
||||||
) -> Result<PathBuf, InvokeError> {
|
|
||||||
let project_dir = app_settings.settings.project.directory.join(project_name);
|
|
||||||
|
|
||||||
if !project_dir.exists() {
|
if !project_dir.exists() {
|
||||||
tokio::fs::create_dir_all(&project_dir)
|
tokio::fs::create_dir_all(&project_dir)
|
||||||
@ -137,11 +134,8 @@ async fn get_project_settings_file_path(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn read_project_settings_file(
|
async fn read_project_settings_file(project_path: &str) -> Result<ProjectConfiguration, InvokeError> {
|
||||||
app_settings: Configuration,
|
let settings_path = get_project_settings_file_path(project_path).await?;
|
||||||
project_name: &str,
|
|
||||||
) -> Result<ProjectConfiguration, InvokeError> {
|
|
||||||
let settings_path = get_project_settings_file_path(app_settings, project_name).await?;
|
|
||||||
|
|
||||||
// Check if this file exists.
|
// Check if this file exists.
|
||||||
if !settings_path.exists() {
|
if !settings_path.exists() {
|
||||||
@ -159,11 +153,10 @@ async fn read_project_settings_file(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn write_project_settings_file(
|
async fn write_project_settings_file(
|
||||||
app_settings: Configuration,
|
project_path: &str,
|
||||||
project_name: &str,
|
|
||||||
configuration: ProjectConfiguration,
|
configuration: ProjectConfiguration,
|
||||||
) -> Result<(), InvokeError> {
|
) -> Result<(), InvokeError> {
|
||||||
let settings_path = get_project_settings_file_path(app_settings, project_name).await?;
|
let settings_path = get_project_settings_file_path(project_path).await?;
|
||||||
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
let contents = toml::to_string_pretty(&configuration).map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
tokio::fs::write(settings_path, contents.as_bytes())
|
tokio::fs::write(settings_path, contents.as_bytes())
|
||||||
.await
|
.await
|
||||||
|
@ -22,8 +22,7 @@ import {
|
|||||||
LspWorker,
|
LspWorker,
|
||||||
} from 'editor/plugins/lsp/types'
|
} from 'editor/plugins/lsp/types'
|
||||||
import { wasmUrl } from 'lang/wasm'
|
import { wasmUrl } from 'lang/wasm'
|
||||||
|
import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||||
const DEFAULT_FILE_NAME: string = 'main.kcl'
|
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
@ -137,7 +136,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (isKclLspServerReady && !TEST && kclLspClient) {
|
if (isKclLspServerReady && !TEST && kclLspClient) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = kclLanguage({
|
const lsp = kclLanguage({
|
||||||
documentUri: `file:///${DEFAULT_FILE_NAME}`,
|
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: kclLspClient,
|
client: kclLspClient,
|
||||||
})
|
})
|
||||||
@ -211,7 +210,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (isCopilotLspServerReady && !TEST && copilotLspClient) {
|
if (isCopilotLspServerReady && !TEST && copilotLspClient) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = copilotPlugin({
|
const lsp = copilotPlugin({
|
||||||
documentUri: `file:///${DEFAULT_FILE_NAME}`,
|
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: copilotLspClient,
|
client: copilotLspClient,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
@ -236,7 +235,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
redirect: boolean
|
redirect: boolean
|
||||||
) => {
|
) => {
|
||||||
const currentFilePath = projectBasename(
|
const currentFilePath = projectBasename(
|
||||||
file?.path || DEFAULT_FILE_NAME,
|
file?.path || PROJECT_ENTRYPOINT,
|
||||||
projectPath || ''
|
projectPath || ''
|
||||||
)
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
@ -267,7 +266,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (file) {
|
if (file) {
|
||||||
// Send that the file was opened.
|
// Send that the file was opened.
|
||||||
const filename = projectBasename(
|
const filename = projectBasename(
|
||||||
file?.path || DEFAULT_FILE_NAME,
|
file?.path || PROJECT_ENTRYPOINT,
|
||||||
project?.path || ''
|
project?.path || ''
|
||||||
)
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
@ -285,7 +284,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
const onFileOpen = (filePath: string | null, projectPath: string | null) => {
|
const onFileOpen = (filePath: string | null, projectPath: string | null) => {
|
||||||
const currentFilePath = projectBasename(
|
const currentFilePath = projectBasename(
|
||||||
filePath || DEFAULT_FILE_NAME,
|
filePath || PROJECT_ENTRYPOINT,
|
||||||
projectPath || ''
|
projectPath || ''
|
||||||
)
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
@ -302,7 +301,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
const onFileClose = (filePath: string | null, projectPath: string | null) => {
|
const onFileClose = (filePath: string | null, projectPath: string | null) => {
|
||||||
const currentFilePath = projectBasename(
|
const currentFilePath = projectBasename(
|
||||||
filePath || DEFAULT_FILE_NAME,
|
filePath || PROJECT_ENTRYPOINT,
|
||||||
projectPath || ''
|
projectPath || ''
|
||||||
)
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
|
@ -119,7 +119,7 @@ function ProjectCard({
|
|||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
className="relative z-0 flex flex-col h-full gap-2 p-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10"
|
className="relative z-0 flex flex-col h-full gap-2 p-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10"
|
||||||
to={`${paths.FILE}/${encodeURIComponent(project.path)}`}
|
to={`${paths.FILE}/${encodeURIComponent(project.default_file)}`}
|
||||||
data-testid="project-link"
|
data-testid="project-link"
|
||||||
>
|
>
|
||||||
<div className="flex-1">{project.name?.replace(FILE_EXT, '')}</div>
|
<div className="flex-1">{project.name?.replace(FILE_EXT, '')}</div>
|
||||||
|
@ -24,6 +24,7 @@ const projectWellFormed = {
|
|||||||
},
|
},
|
||||||
kcl_file_count: 1,
|
kcl_file_count: 1,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
|
default_file: '/some/path/Simple Box/main.kcl',
|
||||||
} satisfies Project
|
} satisfies Project
|
||||||
|
|
||||||
describe('ProjectSidebarMenu tests', () => {
|
describe('ProjectSidebarMenu tests', () => {
|
||||||
|
@ -172,7 +172,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
},
|
},
|
||||||
'Execute AST': () => kclManager.executeCode(true),
|
'Execute AST': () => kclManager.executeCode(true),
|
||||||
persistSettings: (context) =>
|
persistSettings: (context) =>
|
||||||
saveSettings(context, loadedProject?.project?.name),
|
saveSettings(context, loadedProject?.project?.path),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -38,8 +38,8 @@ export const settingsLoader: LoaderFunction = async ({
|
|||||||
configuration
|
configuration
|
||||||
)
|
)
|
||||||
if (projectPathData) {
|
if (projectPathData) {
|
||||||
const { project_name } = projectPathData
|
const { project_path } = projectPathData
|
||||||
const { settings: s } = await loadAndValidateSettings(project_name)
|
const { settings: s } = await loadAndValidateSettings(project_path)
|
||||||
settings = s
|
settings = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,6 +118,7 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
children: [],
|
children: [],
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
|
default_file: project_path,
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
name: current_file_name,
|
name: current_file_name,
|
||||||
|
@ -147,7 +147,7 @@ export interface AppSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAndValidateSettings(
|
export async function loadAndValidateSettings(
|
||||||
projectName?: string
|
projectPath?: string
|
||||||
): Promise<AppSettings> {
|
): Promise<AppSettings> {
|
||||||
const settings = createSettings()
|
const settings = createSettings()
|
||||||
const inTauri = isTauri()
|
const inTauri = isTauri()
|
||||||
@ -166,9 +166,9 @@ export async function loadAndValidateSettings(
|
|||||||
setSettingsAtLevel(settings, 'user', appSettingsPayload)
|
setSettingsAtLevel(settings, 'user', appSettingsPayload)
|
||||||
|
|
||||||
// Load the project settings if they exist
|
// Load the project settings if they exist
|
||||||
if (projectName) {
|
if (projectPath) {
|
||||||
const projectSettings = inTauri
|
const projectSettings = inTauri
|
||||||
? await readProjectSettingsFile(appSettings, projectName)
|
? await readProjectSettingsFile(projectPath)
|
||||||
: readLocalStorageProjectSettingsFile()
|
: readLocalStorageProjectSettingsFile()
|
||||||
|
|
||||||
const projectSettingsPayload =
|
const projectSettingsPayload =
|
||||||
@ -182,7 +182,7 @@ export async function loadAndValidateSettings(
|
|||||||
|
|
||||||
export async function saveSettings(
|
export async function saveSettings(
|
||||||
allSettings: typeof settings,
|
allSettings: typeof settings,
|
||||||
projectName?: string
|
projectPath?: string
|
||||||
) {
|
) {
|
||||||
// Make sure we have wasm initialized.
|
// Make sure we have wasm initialized.
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -204,7 +204,7 @@ export async function saveSettings(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!projectName) {
|
if (!projectPath) {
|
||||||
// If we're not saving project settings, we're done.
|
// If we're not saving project settings, we're done.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -217,7 +217,7 @@ export async function saveSettings(
|
|||||||
|
|
||||||
// Write the project settings.
|
// Write the project settings.
|
||||||
if (inTauri) {
|
if (inTauri) {
|
||||||
await writeProjectSettingsFile(appSettings, projectName, projectSettings)
|
await writeProjectSettingsFile(projectPath, projectSettings)
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
localStorageProjectSettingsPath(),
|
localStorageProjectSettingsPath(),
|
||||||
|
@ -143,24 +143,20 @@ export async function writeAppSettingsFile(
|
|||||||
|
|
||||||
// Read project settings file.
|
// Read project settings file.
|
||||||
export async function readProjectSettingsFile(
|
export async function readProjectSettingsFile(
|
||||||
appSettings: Configuration,
|
projectPath: string
|
||||||
projectName: string
|
|
||||||
): Promise<ProjectConfiguration> {
|
): Promise<ProjectConfiguration> {
|
||||||
return await invoke<ProjectConfiguration>('read_project_settings_file', {
|
return await invoke<ProjectConfiguration>('read_project_settings_file', {
|
||||||
appSettings,
|
projectPath,
|
||||||
projectName,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write project settings file.
|
// Write project settings file.
|
||||||
export async function writeProjectSettingsFile(
|
export async function writeProjectSettingsFile(
|
||||||
appSettings: Configuration,
|
projectPath: string,
|
||||||
projectName: string,
|
|
||||||
settings: ProjectConfiguration
|
settings: ProjectConfiguration
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await invoke('write_project_settings_file', {
|
return await invoke('write_project_settings_file', {
|
||||||
appSettings,
|
projectPath,
|
||||||
projectName,
|
|
||||||
configuration: settings,
|
configuration: settings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@ -1,5 +1,6 @@
|
|||||||
//! Types for interacting with files in projects.
|
//! Types for interacting with files in projects.
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@ -7,7 +8,7 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::settings::types::{Configuration, DEFAULT_PROJECT_KCL_FILE};
|
use crate::settings::types::Configuration;
|
||||||
|
|
||||||
/// State management for the application.
|
/// State management for the application.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
@ -48,7 +49,7 @@ impl ProjectState {
|
|||||||
.map_err(|e| anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e))?;
|
.map_err(|e| anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e))?;
|
||||||
|
|
||||||
// Check if we have a main.kcl file in the project.
|
// Check if we have a main.kcl file in the project.
|
||||||
let project_file = source_path.join(DEFAULT_PROJECT_KCL_FILE);
|
let project_file = source_path.join(crate::settings::types::DEFAULT_PROJECT_KCL_FILE);
|
||||||
|
|
||||||
if !project_file.exists() {
|
if !project_file.exists() {
|
||||||
// Create the default file in the project.
|
// Create the default file in the project.
|
||||||
@ -247,6 +248,8 @@ pub struct Project {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[ts(type = "number")]
|
#[ts(type = "number")]
|
||||||
pub directory_count: u64,
|
pub directory_count: u64,
|
||||||
|
/// The default file to open on load.
|
||||||
|
pub default_file: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
@ -266,12 +269,13 @@ impl Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file = crate::settings::utils::walk_dir(&path).await?;
|
let file = crate::settings::utils::walk_dir(&path).await?;
|
||||||
let metadata = std::fs::metadata(path).ok().map(|m| m.into());
|
let metadata = std::fs::metadata(&path).ok().map(|m| m.into());
|
||||||
let mut project = Self {
|
let mut project = Self {
|
||||||
file,
|
file: file.clone(),
|
||||||
metadata,
|
metadata,
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
|
default_file: get_default_kcl_file_for_dir(path, file).await?,
|
||||||
};
|
};
|
||||||
project.populate_kcl_file_count()?;
|
project.populate_kcl_file_count()?;
|
||||||
project.populate_directory_count()?;
|
project.populate_directory_count()?;
|
||||||
@ -309,6 +313,40 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the default KCL file for a directory.
|
||||||
|
/// This determines what the default file to open is.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[async_recursion::async_recursion]
|
||||||
|
pub async fn get_default_kcl_file_for_dir<P>(dir: P, file: FileEntry) -> Result<String>
|
||||||
|
where
|
||||||
|
P: AsRef<Path> + Send,
|
||||||
|
{
|
||||||
|
// Make sure the dir is a directory.
|
||||||
|
if !dir.as_ref().is_dir() {
|
||||||
|
return Err(anyhow::anyhow!("Path `{}` is not a directory", dir.as_ref().display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_file = dir.as_ref().join(crate::settings::types::DEFAULT_PROJECT_KCL_FILE);
|
||||||
|
if !default_file.exists() {
|
||||||
|
// Find a kcl file in the directory.
|
||||||
|
if let Some(children) = file.children {
|
||||||
|
for entry in children.iter() {
|
||||||
|
if entry.name.ends_with(".kcl") {
|
||||||
|
return Ok(dir.as_ref().join(&entry.name).display().to_string());
|
||||||
|
} else if entry.children.is_some() {
|
||||||
|
// Recursively find a kcl file in the directory.
|
||||||
|
return get_default_kcl_file_for_dir(entry.path.clone(), entry.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find a kcl file, create one.
|
||||||
|
tokio::fs::write(&default_file, vec![]).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(default_file.display().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about a file or directory.
|
/// Information about a file or directory.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -588,4 +626,100 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_route_from_route_non_main_file() {
|
||||||
|
let mut configuration = crate::settings::types::Configuration::default();
|
||||||
|
configuration.settings.project.directory =
|
||||||
|
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||||
|
|
||||||
|
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl";
|
||||||
|
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
super::ProjectRoute {
|
||||||
|
project_name: Some("assembly".to_string()),
|
||||||
|
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||||
|
current_file_name: Some("thing.kcl".to_string()),
|
||||||
|
current_file_path: Some(
|
||||||
|
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl".to_string()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_kcl_file_for_dir_non_exist() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let file = crate::settings::utils::walk_dir(&dir).await.unwrap();
|
||||||
|
|
||||||
|
let default_file = super::get_default_kcl_file_for_dir(&dir, file).await.unwrap();
|
||||||
|
assert_eq!(default_file, dir.join("main.kcl").display().to_string());
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_kcl_file_for_dir_main_kcl() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::write(dir.join("main.kcl"), vec![]).unwrap();
|
||||||
|
let file = crate::settings::utils::walk_dir(&dir).await.unwrap();
|
||||||
|
|
||||||
|
let default_file = super::get_default_kcl_file_for_dir(&dir, file).await.unwrap();
|
||||||
|
assert_eq!(default_file, dir.join("main.kcl").display().to_string());
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_kcl_file_for_dir_thing_kcl() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::write(dir.join("thing.kcl"), vec![]).unwrap();
|
||||||
|
let file = crate::settings::utils::walk_dir(&dir).await.unwrap();
|
||||||
|
|
||||||
|
let default_file = super::get_default_kcl_file_for_dir(&dir, file).await.unwrap();
|
||||||
|
assert_eq!(default_file, dir.join("thing.kcl").display().to_string());
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_kcl_file_for_dir_nested_main_kcl() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::create_dir_all(dir.join("assembly")).unwrap();
|
||||||
|
std::fs::write(dir.join("assembly").join("main.kcl"), vec![]).unwrap();
|
||||||
|
let file = crate::settings::utils::walk_dir(&dir).await.unwrap();
|
||||||
|
|
||||||
|
let default_file = super::get_default_kcl_file_for_dir(&dir, file).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
default_file,
|
||||||
|
dir.join("assembly").join("main.kcl").display().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_default_kcl_file_for_dir_nested_thing_kcl() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::create_dir_all(dir.join("assembly")).unwrap();
|
||||||
|
std::fs::write(dir.join("assembly").join("thing.kcl"), vec![]).unwrap();
|
||||||
|
let file = crate::settings::utils::walk_dir(&dir).await.unwrap();
|
||||||
|
|
||||||
|
let default_file = super::get_default_kcl_file_for_dir(&dir, file).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
default_file,
|
||||||
|
dir.join("assembly").join("thing.kcl").display().to_string()
|
||||||
|
);
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ impl Configuration {
|
|||||||
// Because we just created it and it's empty.
|
// Because we just created it and it's empty.
|
||||||
children: None,
|
children: None,
|
||||||
},
|
},
|
||||||
|
default_file: project_file.to_string_lossy().to_string(),
|
||||||
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
||||||
kcl_file_count: 1,
|
kcl_file_count: 1,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
@ -130,7 +131,13 @@ impl Configuration {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
projects.push(self.get_project_info(&e.path().display().to_string()).await?);
|
// Make sure the project has at least one kcl file in it.
|
||||||
|
let project = self.get_project_info(&e.path().display().to_string()).await?;
|
||||||
|
if project.kcl_file_count == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
projects.push(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(projects)
|
Ok(projects)
|
||||||
@ -150,11 +157,14 @@ impl Configuration {
|
|||||||
return Err(anyhow::anyhow!("Project path is not a directory: {}", project_path));
|
return Err(anyhow::anyhow!("Project path is not a directory: {}", project_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let walked = crate::settings::utils::walk_dir(project_dir).await?;
|
||||||
|
|
||||||
let mut project = crate::settings::types::file::Project {
|
let mut project = crate::settings::types::file::Project {
|
||||||
file: crate::settings::utils::walk_dir(project_dir).await?,
|
file: walked.clone(),
|
||||||
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
metadata: Some(tokio::fs::metadata(&project_dir).await?.into()),
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
|
default_file: crate::settings::types::file::get_default_kcl_file_for_dir(project_dir, walked).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate the number of KCL files in the project.
|
// Populate the number of KCL files in the project.
|
||||||
|
@ -33,6 +33,16 @@ pub async fn walk_dir<P>(dir: P) -> Result<FileEntry>
|
|||||||
where
|
where
|
||||||
P: AsRef<Path> + Send,
|
P: AsRef<Path> + Send,
|
||||||
{
|
{
|
||||||
|
// Make sure the path is a directory.
|
||||||
|
if !dir.as_ref().is_dir() {
|
||||||
|
return Err(anyhow::anyhow!("Path `{}` is not a directory", dir.as_ref().display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the directory exists.
|
||||||
|
if !dir.as_ref().exists() {
|
||||||
|
return Err(anyhow::anyhow!("Directory `{}` does not exist", dir.as_ref().display()));
|
||||||
|
}
|
||||||
|
|
||||||
let mut entry = FileEntry {
|
let mut entry = FileEntry {
|
||||||
name: dir
|
name: dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
@ -77,16 +77,6 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Enter edit mode.
|
|
||||||
// We can't get control points of an existing sketch without being in edit mode.
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
SourceRange::default(),
|
|
||||||
ModelingCmd::EditModeEnter { target: sketch_id },
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok((ctx, program, sketch_id))
|
Ok((ctx, program, sketch_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|