fix(editor): file modification status is not updated while closing files (fixes #1485)

This commit is contained in:
Akash Yadav
2023-11-25 23:31:48 +05:30
parent b2ff61043b
commit e1382f13a7
5 changed files with 142 additions and 132 deletions
-1
View File
@@ -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()
@@ -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)