Update editor: build using temp files and save directly to source

This commit is contained in:
Hasinjato 2026-05-19 12:21:26 +03:00
parent d61ec9e1f2
commit 1223416923
5 changed files with 116 additions and 18 deletions

View file

@ -0,0 +1,22 @@
package mg.dot.feufaro
class AndroidPathValidator : PathValidator {
override fun isRestrictedPath(path: String): Boolean {
if (path.isEmpty()) return true
val lowerPath = path.lowercase()
val isAndroidAsset = lowerPath.contains("android_asset") ||
lowerPath.startsWith("assets/")
val isAndroidCacheOrTmp = lowerPath.contains("/cache/") ||
lowerPath.contains("/code_cache/") ||
lowerPath.contains("/tmp/") ||
lowerPath.contains("_tmp")
return isAndroidAsset || isAndroidCacheOrTmp
}
}
actual fun getPathValidator(): PathValidator = AndroidPathValidator()

View file

@ -0,0 +1,5 @@
package mg.dot.feufaro
interface PathValidator {
fun isRestrictedPath(path: String): Boolean
}

View file

@ -1,7 +1,10 @@
package mg.dot.feufaro package mg.dot.feufaro
interface Platform { interface Platform {
val name: String val name: String
} }
expect fun getPlatform(): Platform expect fun getPlatform(): Platform
expect fun getPathValidator(): PathValidator

View file

@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBackIos
import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
@ -51,6 +50,7 @@ import mg.dot.feufaro.ContextualMenu
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.TUODetailDialog import mg.dot.feufaro.TUODetailDialog
import mg.dot.feufaro.data.GridTUOData import mg.dot.feufaro.data.GridTUOData
import mg.dot.feufaro.getPathValidator
import mg.dot.feufaro.ui.rememberFileSaveLauncher import mg.dot.feufaro.ui.rememberFileSaveLauncher
import mg.dot.feufaro.viewmodel.MidiMarkers import mg.dot.feufaro.viewmodel.MidiMarkers
import mg.dot.feufaro.viewmodel.SolfaScreenModel import mg.dot.feufaro.viewmodel.SolfaScreenModel
@ -1039,10 +1039,15 @@ private suspend fun expandInclusions(
val lastSlash = currentFilePath.lastIndexOf('/') val lastSlash = currentFilePath.lastIndexOf('/')
val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else "" val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else ""
val inclusionRegex = Regex("^I([0-9]):(.*)")
lines.forEach { line -> lines.forEach { line ->
if (line.startsWith("I0:")) { val match = inclusionRegex.find(line)
if (match != null) {
try { try {
val parts = line.substring(3).split(":") val body = match.groupValues[2]
val parts = body.split(":")
val fileName = parts[0] val fileName = parts[0]
val ignorePattern = if (parts.size > 1) parts[1] else "" val ignorePattern = if (parts.size > 1) parts[1] else ""
@ -1057,7 +1062,7 @@ private suspend fun expandInclusions(
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
result.append("// Erreur inclusion: ${e.message}\n") result.append("// Erreur inclusion sur ${line}: ${e.message}\n")
} }
} else { } else {
result.append(line).append("\n") result.append(line).append("\n")
@ -1092,8 +1097,20 @@ fun EditSourceCompose(
var currentTempFile by remember { mutableStateOf<File?>(null) } var currentTempFile by remember { mutableStateOf<File?>(null) }
var codeContent by remember { mutableStateOf(sourceContent?: "") } var codeContent by remember { mutableStateOf(sourceContent?: "") }
val pathValidator = remember { getPathValidator() }
// restricted path pour les fichiers interne de l'app
val isRestrictedPath = remember(sourcePath) {
pathValidator.isRestrictedPath(sourcePath)
}
val isModified = remember(codeContent, sourceContent) { codeContent != sourceContent }
var previousCodeContentBeforeBuild by remember { mutableStateOf<String?>(null) }
LaunchedEffect(sourceContent) { LaunchedEffect(sourceContent) {
codeContent = sourceContent codeContent = sourceContent
previousCodeContentBeforeBuild = null
} }
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) } var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
@ -1140,6 +1157,8 @@ fun EditSourceCompose(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
EditorActionButtons( EditorActionButtons(
isSaveVisible = isModified || isRestrictedPath,
isUndoVisible = isModified,
onUndo = { onUndo = {
scope.launch { scope.launch {
codeContent = sourceContent codeContent = sourceContent
@ -1162,19 +1181,20 @@ fun EditSourceCompose(
if (currentTempFile == null || !currentTempFile!!.exists()) { if (currentTempFile == null || !currentTempFile!!.exists()) {
val tempDir = System.getProperty("java.io.tmpdir") val tempDir = System.getProperty("java.io.tmpdir")
currentTempFile = File(tempDir, fileName) currentTempFile = File(tempDir, fileName.ifEmpty { "preview_solfa.txt" })
currentTempFile?.deleteOnExit() currentTempFile?.deleteOnExit()
} }
currentTempFile?.writeText(expandedContent) withContext(Dispatchers.IO) {
currentTempFile?.writeText(expandedContent)
currentTempFile?.let { file ->
withContext(Dispatchers.Main) {
solfaScreenModel.justeCompile(file.absolutePath)
}
} }
println("Compilation sur le fichier : ${currentTempFile?.absolutePath}") withContext(Dispatchers.Main) {
solfaScreenModel.justeCompile(currentTempFile!!.absolutePath)
println("Compilation temporaire réussie : ${currentTempFile?.absolutePath}")
currentTempFile?.deleteOnExit()
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -1184,8 +1204,23 @@ fun EditSourceCompose(
onSave = { onSave = {
scope.launch { scope.launch {
try { try {
val initialDir = solfaScreenModel.fileRepository.getAppPublicFolder().absolutePath if (!isRestrictedPath) {
saveLauncher.launch(fileName, initialDir) val externalFile = File(sourcePath)
withContext(Dispatchers.IO) {
externalFile.writeText(codeContent)
}
withContext(Dispatchers.Main) {
sharedScreenModel.setFileContent(codeContent, sourcePath)
solfaScreenModel.loadExternalFile(sourcePath)
}
println("Vraie source écrasée avec succès : $sourcePath")
} else {
val initialDir = solfaScreenModel.fileRepository.getAppPublicFolder().absolutePath
saveLauncher.launch(fileName, initialDir)
}
currentTempFile?.delete()
currentTempFile = null
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@ -1221,11 +1256,15 @@ fun EditSourceCompose(
} }
@Composable @Composable
fun EditorActionButtons(onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> Unit, onClose: () -> Unit) { fun EditorActionButtons(isSaveVisible: Boolean, isUndoVisible: Boolean, onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> Unit, onClose: () -> Unit) {
Row(horizontalArrangement = Arrangement.SpaceBetween) { Row(horizontalArrangement = Arrangement.SpaceBetween) {
IconButton(onClick = onUndo) { Icon(Icons.AutoMirrored.Filled.Undo, null, tint = Color.Gray) } if(isUndoVisible) {
IconButton(onClick = onUndo) { Icon(Icons.AutoMirrored.Filled.Undo, null, tint = Color.Gray) }
}
IconButton(onClick = onBuild) { Icon(Icons.Default.Build, null, tint = Color(0xFF2196F3)) } IconButton(onClick = onBuild) { Icon(Icons.Default.Build, null, tint = Color(0xFF2196F3)) }
IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) } if(isSaveVisible) {
IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) }
}
IconButton(onClick = onClose) { Icon(Icons.Default.Close, null, tint = Color.Red) } IconButton(onClick = onClose) { Icon(Icons.Default.Close, null, tint = Color.Red) }
} }
} }

View file

@ -0,0 +1,29 @@
package mg.dot.feufaro
import java.io.File
class DesktopPathValidator : PathValidator {
private val systemTmpDir: String by lazy {
try {
File(System.getProperty("java.io.tmpdir")).absolutePath.lowercase()
} catch (e: Exception) {
"/tmp"
}
}
override fun isRestrictedPath(path: String): Boolean {
if (path.isEmpty()) return true
val lowerPath = path.lowercase()
val isInSystemTmp = lowerPath.contains(systemTmpDir)
val isOtherRestricted = lowerPath.contains("/tmp/") ||
lowerPath.contains("_tmp") ||
lowerPath.contains("assets")
return isInSystemTmp || isOtherRestricted
}
}
actual fun getPathValidator(): PathValidator = DesktopPathValidator()