[Fix] Created the safest navigate/load/open/please sir i want some more for a .kcl file (#6867)

* fix: clear scene and bust cache if rust panics

* Update onboarding following @jgomez720

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* fix: clean up

* fix: FUCK

* fix: FUCK 2.0

* fix: oh boi

* fix: oh boi

* fix: idk man

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix: take main on this, do not need a single line from my testing code

* fix: more PR cleanup from all of the testing code

* fix: trying to clean up more, ope this has a lot of other code

* fix: PR clean up

* fix: trying to get a clean branch, I had multiple other branches in here ope

* fix: more cleanup

* fix: another one

* fix: fixed the comment to be accurate

* fix: removed confusing comment

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Kevin Nadro
2025-05-13 10:35:29 -05:00
committed by GitHub
parent 13dbfdfaa4
commit 99cb6a6179
4 changed files with 128 additions and 3 deletions

View File

@ -261,7 +261,14 @@ export const EngineStream = (props: {
)
.catch(trap)
}
}, [file?.path])
/**
* Watch file not file?.path. Watching the object allows us to send the same file.path back to back
* and still trigger the executeCode() function. JS should not be doing a cache check on the file path
* we should be putting the cache check in Rust.
* e.g. We can call `navigate(/file/<>)` or `navigate(/file/<>/settings)` as much as we want and it will
* trigger this workflow.
*/
}, [file])
const IDLE_TIME_MS = Number(streamIdleMode)

View File

@ -4,12 +4,16 @@ import {
joinRouterPaths,
joinOSPaths,
safeEncodeForRouterPaths,
webSafePathSplit,
getProjectDirectoryFromKCLFilePath,
} from '@src/lib/paths'
import {
billingActor,
systemIOActor,
useSettings,
useToken,
kclManager,
engineCommandManager,
} from '@src/lib/singletons'
import { BillingTransition } from '@src/machines/billingMachine'
import {
@ -29,6 +33,10 @@ import { useEffect } from 'react'
import { submitAndAwaitTextToKclSystemIO } from '@src/lib/textToCad'
import { reportRejection } from '@src/lib/trap'
import { getUniqueProjectName } from '@src/lib/desktopFS'
import { useLspContext } from '@src/components/LspProvider'
import { useLocation } from 'react-router-dom'
import makeUrlPathRelative from '@src/lib/makeUrlPathRelative'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from '@src/lib/constants'
export function SystemIOMachineLogicListenerDesktop() {
const requestedProjectName = useRequestedProjectName()
@ -40,7 +48,73 @@ export function SystemIOMachineLogicListenerDesktop() {
const requestedTextToCadGeneration = useRequestedTextToCadGeneration()
const token = useToken()
const folders = useFolders()
const { onFileOpen, onFileClose } = useLspContext()
const { pathname } = useLocation()
function safestNavigateToFile({
requestedPath,
requestedFilePathWithExtension,
requestedProjectDirectory,
}: {
requestedPath: string
requestedFilePathWithExtension: string | null
requestedProjectDirectory: string | null
}) {
let filePathWithExtension = null
let projectDirectory = null
// assumes /file/<encodedURIComponent>
// e.g '/file/%2Fhome%2Fkevin-nadro%2FDocuments%2Fzoo-modeling-app-projects%2Fbracket-1%2Fbracket.kcl'
const [iAmABlankString, file, encodedURI] = webSafePathSplit(pathname)
if (
iAmABlankString === '' &&
file === makeUrlPathRelative(PATHS.FILE) &&
encodedURI
) {
filePathWithExtension = decodeURIComponent(encodedURI)
const applicationProjectDirectory = settings.app.projectDirectory.current
projectDirectory = getProjectDirectoryFromKCLFilePath(
filePathWithExtension,
applicationProjectDirectory
)
}
// Close current file in current project if it exists
onFileClose(filePathWithExtension, projectDirectory)
// Open the requested file in the requested project
onFileOpen(requestedFilePathWithExtension, requestedProjectDirectory)
engineCommandManager.rejectAllModelingCommands(
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
)
/**
* Check that both paths are truthy strings and if they do not match
* then mark it is switchedFiles.
* If they do not match but the origin is falsey we do not want to mark as
* switchedFiles because checkIfSwitchedFilesShouldClear will trigger
* clearSceneAndBustCache if there is a parse error!
*
* i.e. Only do switchedFiles check against two file paths, not null and a file path
*/
if (
filePathWithExtension &&
requestedFilePathWithExtension &&
filePathWithExtension !== requestedFilePathWithExtension
) {
kclManager.switchedFiles = true
}
kclManager.isExecuting = false
navigate(requestedPath)
}
/**
* We watch objects because we want to be able to navigate to itself
* if we used a string the useEffect would not change
* e.g. context.projectName if this was a string we would not be able to
* navigate to CoolProject N times in a row. If we wrap this in an object
* the object is watched not the string value
*/
const useGlobalProjectNavigation = () => {
useEffect(() => {
if (!requestedProjectName.name) {
@ -55,10 +129,21 @@ export function SystemIOMachineLogicListenerDesktop() {
safeEncodeForRouterPaths(projectPathWithoutSpecificKCLFile),
requestedProjectName.subRoute || ''
)
navigate(requestedPath)
safestNavigateToFile({
requestedPath,
requestedFilePathWithExtension: null,
requestedProjectDirectory: projectPathWithoutSpecificKCLFile,
})
}, [requestedProjectName])
}
/**
* We watch objects because we want to be able to navigate to itself
* if we used a string the useEffect would not change
* e.g. context.projectName if this was a string we would not be able to
* navigate to coolFile.kcl N times in a row. If we wrap this in an object
* the object is watched not the string value
*/
const useGlobalFileNavigation = () => {
useEffect(() => {
if (!requestedFileName.file || !requestedFileName.project) {
@ -69,12 +154,20 @@ export function SystemIOMachineLogicListenerDesktop() {
requestedFileName.project,
requestedFileName.file
)
const projectPathWithoutSpecificKCLFile = joinOSPaths(
projectDirectoryPath,
requestedProjectName.name
)
const requestedPath = joinRouterPaths(
PATHS.FILE,
safeEncodeForRouterPaths(filePath),
requestedFileName.subRoute || ''
)
navigate(requestedPath)
safestNavigateToFile({
requestedPath,
requestedFilePathWithExtension: filePath,
requestedProjectDirectory: projectPathWithoutSpecificKCLFile,
})
}, [requestedFileName])
}

View File

@ -172,6 +172,27 @@ export function getStringAfterLastSeparator(path: string): string {
return path.split(window.electron.sep).pop() || ''
}
/**
* When you have '/home/kevin-nadro/Documents/zoo-modeling-app-projects/bracket-1/bracket.kcl'
* and you need to get the projectDirectory from this string above.
*
* We replace the leading prefix which is the application project directory with
* the empty string. Then it becomes //bracket-1/bracket.kcl
* The first part of the path after the blank / will be the root project directory
*
*/
export function getProjectDirectoryFromKCLFilePath(
path: string,
applicationProjectDirectory: string
): string {
const replacedPath = path.replace(applicationProjectDirectory, '')
const [iAmABlankString, projectDirectory] = desktopSafePathSplit(replacedPath)
if (iAmABlankString === '') {
return projectDirectory
}
return ''
}
/**
* Use this for only web related paths not paths in OS or on disk
* e.g. document.location.pathname

View File

@ -84,6 +84,10 @@ export type SystemIOContext = {
/** has the application gone through the initialization of systemIOMachine at least once.
* this is required to prevent chokidar from spamming invalid events during initialization. */
hasListedProjects: boolean
/**
* We watch objects because we want to be able to navigate to itself
* if we used a string the useEffect would not change
*/
requestedProjectName: { name: string; subRoute?: string }
requestedFileName: { project: string; file: string; subRoute?: string }
canReadWriteProjectDirectory: { value: boolean; error: unknown }