mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(editor): file modification status is not updated while closing files (fixes #1485)
This commit is contained in:
Generated
-1
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ANTLRGenerationPreferences">
|
||||
<option name="perGrammarGenerationSettings">
|
||||
|
||||
@@ -465,6 +465,8 @@ abstract class BaseEditorActivity :
|
||||
viewContainer.displayedChild = 0
|
||||
}
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
setupNoEditorView()
|
||||
|
||||
+100
-98
@@ -28,7 +28,6 @@ import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.collection.MutableIntObjectMap
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.blankj.utilcode.util.ImageUtils
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR
|
||||
@@ -51,7 +50,6 @@ import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.models.SaveResult
|
||||
import com.itsaky.androidide.projects.ProjectManagerImpl
|
||||
import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.tasks.executeAsyncProvideError
|
||||
import com.itsaky.androidide.ui.CodeEditorView
|
||||
import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog
|
||||
import com.itsaky.androidide.utils.IntentUtils.openImage
|
||||
@@ -109,6 +107,9 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
editorViewModel._filesModified.observe(this) { invalidateOptionsMenu() }
|
||||
editorViewModel._filesSaving.observe(this) { invalidateOptionsMenu() }
|
||||
|
||||
editorViewModel.observeFiles(this) {
|
||||
// rewrite the cached files index if there are any opened files
|
||||
val currentFile =
|
||||
@@ -413,41 +414,40 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
}
|
||||
|
||||
override suspend fun saveAllResult(progressConsumer: ((Int, Int) -> Unit)?): SaveResult {
|
||||
setFilesSaving(true)
|
||||
val result = SaveResult()
|
||||
for (i in 0 until editorViewModel.getOpenedFileCount()) {
|
||||
saveResultInternal(i, result, false)
|
||||
progressConsumer?.invoke(i + 1, editorViewModel.getOpenedFileCount())
|
||||
return performFileSave {
|
||||
val result = SaveResult()
|
||||
for (i in 0 until editorViewModel.getOpenedFileCount()) {
|
||||
saveResultInternal(i, result)
|
||||
progressConsumer?.invoke(i + 1, editorViewModel.getOpenedFileCount())
|
||||
}
|
||||
|
||||
return@performFileSave result
|
||||
}
|
||||
setFilesSaving(false)
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun saveResult(index: Int, result: SaveResult) {
|
||||
saveResultInternal(index, result, true)
|
||||
performFileSave {
|
||||
saveResultInternal(index, result)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveResultInternal(index: Int, result: SaveResult, updateSaving: Boolean) {
|
||||
private suspend fun saveResultInternal(
|
||||
index: Int,
|
||||
result: SaveResult
|
||||
) : Boolean {
|
||||
if (index < 0 || index >= editorViewModel.getOpenedFileCount()) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
val frag = getEditorAtIndex(index) ?: return
|
||||
val fileName = frag.file?.name ?: return
|
||||
|
||||
if (updateSaving) {
|
||||
setFilesSaving(true)
|
||||
}
|
||||
val frag = getEditorAtIndex(index) ?: return false
|
||||
val fileName = frag.file?.name ?: return false
|
||||
|
||||
run {
|
||||
// Must be called before frag.save()
|
||||
// Otherwise, it'll always return false
|
||||
val modified = frag.isModified
|
||||
if (!frag.save()) {
|
||||
if (updateSaving) {
|
||||
setFilesSaving(false)
|
||||
}
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
val isGradle = fileName.endsWith(".gradle") || fileName.endsWith(".gradle.kts")
|
||||
@@ -461,22 +461,11 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
var modified = false
|
||||
for (file in editorViewModel.getOpenedFiles()) {
|
||||
val editor = getEditorForFile(file) ?: continue
|
||||
modified = modified || editor.isModified
|
||||
}
|
||||
|
||||
val finalModified = modified
|
||||
val hasUnsaved = hasUnsavedFiles()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
editorViewModel.apply {
|
||||
setFilesModified(finalModified)
|
||||
}
|
||||
|
||||
if (updateSaving) {
|
||||
setFilesSaving(false)
|
||||
}
|
||||
editorViewModel.areFilesModified = hasUnsaved
|
||||
|
||||
// set tab as unmodified
|
||||
val tab = binding.tabs.getTabAt(index) ?: return@withContext
|
||||
@@ -484,85 +473,105 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
tab.text = tab.text!!.substring(startIndex = 1)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hasUnsavedFiles() = editorViewModel.getOpenedFiles().any { file ->
|
||||
getEditorForFile(file)?.isModified == true
|
||||
}
|
||||
|
||||
private suspend inline fun <T : Any?> performFileSave(crossinline action: suspend () -> T) : T {
|
||||
setFilesSaving(true)
|
||||
try {
|
||||
return action()
|
||||
} finally {
|
||||
setFilesSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setFilesSaving(saving: Boolean) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
invalidateOptionsMenu()
|
||||
editorViewModel.setFilesSaving(saving)
|
||||
editorViewModel.areFilesSaving = saving
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEditorContentChanged() {
|
||||
editorViewModel.setFilesModified(true)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun areFilesModified(): Boolean {
|
||||
return editorViewModel.areFilesModified()
|
||||
return editorViewModel.areFilesModified
|
||||
}
|
||||
|
||||
override fun areFilesSaving(): Boolean {
|
||||
return editorViewModel.areFilesSaving()
|
||||
return editorViewModel.areFilesSaving
|
||||
}
|
||||
|
||||
override fun closeFile(index: Int, runAfter: () -> Unit) {
|
||||
if (index >= 0 && index < editorViewModel.getOpenedFileCount()) {
|
||||
val opened: File = editorViewModel.getOpenedFile(index)
|
||||
log.info("Closing file:", opened)
|
||||
val editor = getEditorAtIndex(index)
|
||||
if (editor != null && editor.isModified) {
|
||||
notifyFilesUnsaved(listOf(editor)) { closeFile(index, runAfter) }
|
||||
return
|
||||
}
|
||||
|
||||
editor?.close() ?: run {
|
||||
log.error("Cannot save file before close. Editor instance is null")
|
||||
}
|
||||
|
||||
editorViewModel.removeFile(index)
|
||||
binding.apply {
|
||||
tabs.removeTabAt(index)
|
||||
editorContainer.removeViewAt(index)
|
||||
}
|
||||
|
||||
updateTabs()
|
||||
} else {
|
||||
if (index < 0 || index >= editorViewModel.getOpenedFileCount()) {
|
||||
log.error("Invalid file index. Cannot close.")
|
||||
return
|
||||
}
|
||||
|
||||
val opened = editorViewModel.getOpenedFile(index)
|
||||
log.info("Closing file:", opened)
|
||||
|
||||
val editor = getEditorAtIndex(index)
|
||||
if (editor?.isModified == true) {
|
||||
log.info("File has been modified:", opened)
|
||||
notifyFilesUnsaved(listOf(editor)) {
|
||||
closeFile(index, runAfter)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
editor?.close() ?: run {
|
||||
log.error("Cannot save file before close. Editor instance is null")
|
||||
}
|
||||
|
||||
editorViewModel.removeFile(index)
|
||||
binding.apply {
|
||||
tabs.removeTabAt(index)
|
||||
editorContainer.removeViewAt(index)
|
||||
}
|
||||
|
||||
editorViewModel.areFilesModified = hasUnsavedFiles()
|
||||
|
||||
updateTabs()
|
||||
runAfter()
|
||||
}
|
||||
|
||||
override fun closeOthers() {
|
||||
if (editorViewModel.getOpenedFileCount() == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val unsavedFiles =
|
||||
editorViewModel.getOpenedFiles().map(::getEditorForFile)
|
||||
.filter { it != null && it.isModified }
|
||||
|
||||
if (unsavedFiles.isEmpty()) {
|
||||
val file = editorViewModel.getCurrentFile()
|
||||
var index = 0
|
||||
|
||||
// keep closing the file at index 0
|
||||
// if openedFiles[0] == file, then keep closing files at index 1
|
||||
while (editorViewModel.getOpenedFileCount() != 1) {
|
||||
val editor = getEditorAtIndex(index)
|
||||
|
||||
// Index of files changes as we keep close files
|
||||
// So we compare the files instead of index
|
||||
if (editor != null) {
|
||||
if (file != editor.file) {
|
||||
closeFile(index)
|
||||
} else {
|
||||
index = 1
|
||||
}
|
||||
} else {
|
||||
log.error("Unable to save file at index:", index)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (unsavedFiles.isNotEmpty()) {
|
||||
notifyFilesUnsaved(unsavedFiles) { closeOthers() }
|
||||
return
|
||||
}
|
||||
|
||||
val file = editorViewModel.getCurrentFile()
|
||||
var index = 0
|
||||
|
||||
// keep closing the file at index 0
|
||||
// if openedFiles[0] == file, then keep closing files at index 1
|
||||
while (editorViewModel.getOpenedFileCount() != 1) {
|
||||
val editor = getEditorAtIndex(index)
|
||||
|
||||
if (editor == null) {
|
||||
log.error("Unable to save file at index:", index)
|
||||
continue
|
||||
}
|
||||
|
||||
// Index of files changes as we keep close files
|
||||
// So we compare the files instead of index
|
||||
if (file != editor.file) {
|
||||
closeFile(index)
|
||||
} else {
|
||||
index = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +659,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDocumentChange(event: DocumentChangeEvent) {
|
||||
// update content modification status
|
||||
onEditorContentChanged()
|
||||
editorViewModel.areFilesModified = true
|
||||
|
||||
val index = findIndexOfEditorByFile(event.file.toFile())
|
||||
if (index == -1) {
|
||||
@@ -667,7 +676,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
}
|
||||
|
||||
private fun updateTabs() {
|
||||
executeAsyncProvideError({
|
||||
editorActivityScope.launch {
|
||||
val files = editorViewModel.getOpenedFiles()
|
||||
val dupliCount = mutableMapOf<String, Int>()
|
||||
val names = MutableIntObjectMap<String>()
|
||||
@@ -690,15 +699,8 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
|
||||
names[i] = name
|
||||
}
|
||||
|
||||
names
|
||||
}) { result, error ->
|
||||
if (result == null || error != null) {
|
||||
log.error("Failed to compute names for file tabs", error)
|
||||
return@executeAsyncProvideError
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
result.forEach { index, name ->
|
||||
withContext(Dispatchers.Main) {
|
||||
names.forEach { index, name ->
|
||||
binding.tabs.getTabAt(index)?.text = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ class CodeEditorView(
|
||||
* Mark this files as saved. Even if it not saved.
|
||||
*/
|
||||
fun markAsSaved() {
|
||||
notifySaved()
|
||||
editor?.markUnmodified()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,6 +245,7 @@ class CodeEditorView(
|
||||
_binding?.rwProgress?.isVisible = false
|
||||
}
|
||||
|
||||
markUnmodified()
|
||||
notifySaved()
|
||||
|
||||
return true
|
||||
|
||||
@@ -24,10 +24,12 @@ import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.blankj.utilcode.util.FileUtils
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.itsaky.androidide.models.OpenedFile
|
||||
import com.itsaky.androidide.models.OpenedFilesCache
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.File
|
||||
|
||||
/** ViewModel for data used in [com.itsaky.androidide.activities.editor.EditorActivityKt] */
|
||||
@@ -41,12 +43,12 @@ class EditorViewModel : ViewModel() {
|
||||
internal val _startDrawerOpened = MutableLiveData(false)
|
||||
internal val _isSyncNeeded = MutableLiveData(false)
|
||||
|
||||
internal val _filesModified = MutableLiveData(false)
|
||||
internal val _filesSaving = MutableLiveData(false)
|
||||
|
||||
private val _openedFiles = MutableLiveData<OpenedFilesCache>()
|
||||
private val _isBoundToBuildService = MutableLiveData(false)
|
||||
private val files = MutableLiveData<MutableList<File>>(ArrayList())
|
||||
|
||||
private val fileModified = MutableLiveData(false)
|
||||
private val fileSaving = MutableLiveData(false)
|
||||
private val _files = MutableLiveData<MutableList<File>>(ArrayList())
|
||||
|
||||
/**
|
||||
* Holds information about the currently selected editor fragment. First value in the pair is the
|
||||
@@ -54,6 +56,19 @@ class EditorViewModel : ViewModel() {
|
||||
*/
|
||||
private val mCurrentFile = MutableLiveData<Pair<Int, File?>?>(null)
|
||||
|
||||
var areFilesModified: Boolean
|
||||
get() = _filesModified.value ?: false
|
||||
set(value) {
|
||||
ILogger.instance().debug("update areFilesModified = $value", RuntimeException())
|
||||
_filesModified.value = value
|
||||
}
|
||||
|
||||
var areFilesSaving: Boolean
|
||||
get() = _filesSaving.value ?: false
|
||||
set(value) {
|
||||
_filesSaving.value = value
|
||||
}
|
||||
|
||||
var openedFilesCache: OpenedFilesCache?
|
||||
get() = _openedFiles.value
|
||||
set(value) {
|
||||
@@ -108,15 +123,24 @@ class EditorViewModel : ViewModel() {
|
||||
_isSyncNeeded.value = value
|
||||
}
|
||||
|
||||
internal var files: MutableList<File>
|
||||
get() = this._files.value ?: EMPTY_FILES
|
||||
set(value) {
|
||||
this._files.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EMPTY_FILES = mutableListOf<File>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given file to the list of opened files.
|
||||
*
|
||||
* @param file The file that has been opened.
|
||||
*/
|
||||
fun addFile(file: File) {
|
||||
val files = files.value ?: mutableListOf()
|
||||
files.add(file)
|
||||
this.files.value = files
|
||||
this.files = files
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,17 +149,16 @@ class EditorViewModel : ViewModel() {
|
||||
* @param index The index of the closed file.
|
||||
*/
|
||||
fun removeFile(index: Int) {
|
||||
val files = files.value ?: mutableListOf()
|
||||
files.removeAt(index)
|
||||
this.files.value = files
|
||||
this.files = files
|
||||
|
||||
if (this.files.value?.isEmpty() == true) {
|
||||
if (this.files.isEmpty()) {
|
||||
mCurrentFile.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAllFiles() {
|
||||
files.value = mutableListOf()
|
||||
this.files = EMPTY_FILES
|
||||
setCurrentFile(-1, null)
|
||||
}
|
||||
|
||||
@@ -145,8 +168,8 @@ class EditorViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun updateFile(index: Int, newFile: File) {
|
||||
val files = files.value ?: return
|
||||
files[index] = newFile
|
||||
this.files = files
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +179,7 @@ class EditorViewModel : ViewModel() {
|
||||
* @return The file at the given index.
|
||||
*/
|
||||
fun getOpenedFile(index: Int): File {
|
||||
return files.value!![index]
|
||||
return files[index]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +188,7 @@ class EditorViewModel : ViewModel() {
|
||||
* @return The number of files opened.
|
||||
*/
|
||||
fun getOpenedFileCount(): Int {
|
||||
return files.value!!.size
|
||||
return files.size
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +197,7 @@ class EditorViewModel : ViewModel() {
|
||||
* @return The list of opened files.
|
||||
*/
|
||||
fun getOpenedFiles(): List<File> {
|
||||
return files.value ?: mutableListOf()
|
||||
return files
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +207,7 @@ class EditorViewModel : ViewModel() {
|
||||
* @param observer The observer.
|
||||
*/
|
||||
fun observeFiles(lifecycleOwner: LifecycleOwner?, observer: Observer<MutableList<File>?>?) {
|
||||
files.observe(lifecycleOwner!!, observer!!)
|
||||
_files.observe(lifecycleOwner!!, observer!!)
|
||||
}
|
||||
|
||||
fun getCurrentFileIndex(): Int {
|
||||
@@ -195,23 +218,6 @@ class EditorViewModel : ViewModel() {
|
||||
return mCurrentFile.value?.second
|
||||
}
|
||||
|
||||
fun setFilesModified(modified: Boolean) {
|
||||
fileModified.value = modified
|
||||
}
|
||||
|
||||
fun areFilesModified(): Boolean {
|
||||
val modified = fileModified.value
|
||||
return modified != null && modified
|
||||
}
|
||||
|
||||
fun setFilesSaving(saving: Boolean) {
|
||||
fileSaving.value = saving
|
||||
}
|
||||
|
||||
fun areFilesSaving(): Boolean {
|
||||
return fileSaving.value ?: false
|
||||
}
|
||||
|
||||
fun readOpenedFiles(result: (OpenedFilesCache?) -> Unit) {
|
||||
executeAsync({
|
||||
val file = getOpenedFilesCache(false)
|
||||
|
||||
Reference in New Issue
Block a user