Auto-Save implementation for project files (issue SpartanJ/ecode#220). Still pending Auto-Save for non-open project environment.

This commit is contained in:
Martín Lucas Golini
2024-07-14 20:15:04 -03:00
parent a836f6335e
commit 8ce9e7045b
11 changed files with 439 additions and 127 deletions
+4 -5
View File
@@ -23,10 +23,6 @@
#include <unordered_map>
#include <vector>
#define MAX_KEYNAME 128
#define MAX_VALUENAME 128
#define MAX_VALUEDATA 2048
namespace EE { namespace System {
class Pack;
@@ -65,7 +61,7 @@ class EE_API IniFile {
void path( const std::string& newPath ) { mPath = newPath; }
/** @return The ini file path */
std::string path() const { return mPath; }
const std::string& path() const { return mPath; }
/** Reads ini file specified using mPath.
* @return true if successful, false otherwise. */
@@ -74,6 +70,9 @@ class EE_API IniFile {
/** Writes data stored in class to ini file. */
bool writeFile();
/** Writes data stored in class to a IOStream. */
bool writeStream( IOStream& stream );
/** Deletes all stored ini data. */
void clear();
+11 -1
View File
@@ -631,6 +631,14 @@ class EE_API TextDocument {
void setLines( std::vector<TextDocumentLine>&& lines );
std::string serializeUndoRedo( bool inverted );
void unserializeUndoRedo( const std::string& jsonString );
void changeFilePath( const std::string& filePath );
void setDirtyUntilSave();
protected:
friend class TextUndoStack;
friend class FoldRangeServive;
@@ -672,7 +680,7 @@ class EE_API TextDocument {
Clock mTimer;
SyntaxDefinition mSyntaxDefinition;
std::string mDefaultFileName;
Uint64 mCleanChangeId;
Uint64 mCleanChangeId{ 0 };
Uint32 mPageSize{ 10 };
UnorderedMap<std::string, DocumentCommand> mCommands;
UnorderedMap<std::string, DocumentRefCommand> mRefCommands;
@@ -754,6 +762,8 @@ class EE_API TextDocument {
bool wholeWord = false,
FindReplaceType type = FindReplaceType::Normal,
TextRange restrictRange = TextRange() );
void changeFilePath( const std::string& filePath, bool notify );
};
struct TextSearchParams {
+6 -64
View File
@@ -12,71 +12,9 @@ using namespace EE::System;
namespace EE { namespace UI { namespace Doc {
class TextDocument;
class TextUndoCommand;
enum class TextUndoCommandType { Insert, Remove, Selection };
class EE_API TextUndoCommand {
public:
TextUndoCommand( const Uint64& id, const TextUndoCommandType& type, const Time& timestamp );
virtual ~TextUndoCommand();
const Uint64& getId() const;
const TextUndoCommandType& getType() const;
const Time& getTimestamp() const;
protected:
Uint64 mId;
TextUndoCommandType mType;
Time mTimestamp;
};
class EE_API TextUndoCommandInsert : public TextUndoCommand {
public:
TextUndoCommandInsert( const Uint64& id, const size_t& cursorIdx, const String& text,
const TextPosition& position, const Time& timestamp );
const String& getText() const;
const TextPosition& getPosition() const;
size_t getCursorIdx() const;
protected:
String mText;
TextPosition mPosition;
size_t mCursorIdx;
};
class EE_API TextUndoCommandRemove : public TextUndoCommand {
public:
TextUndoCommandRemove( const Uint64& id, const size_t& cursorIdx, const TextRange& range,
const Time& timestamp );
const TextRange& getRange() const;
size_t getCursorIdx() const;
protected:
TextRange mRange;
size_t mCursorIdx;
};
class EE_API TextUndoCommandSelection : public TextUndoCommand {
public:
TextUndoCommandSelection( const Uint64& id, const size_t& cursorIdx,
const TextRanges& selection, const Time& timestamp );
const TextRanges& getSelection() const;
size_t getCursorIdx() const;
protected:
TextRanges mSelection;
size_t mCursorIdx;
};
enum class TextUndoCommandType { Insert = 1, Remove = 2, Selection = 3 };
using UndoStackContainer = std::deque<TextUndoCommand*>;
@@ -108,6 +46,10 @@ class EE_API TextUndoStack {
Uint64 getCurrentChangeId() const;
std::string toJSON( bool inverted );
void fromJSON( const std::string& json );
protected:
friend class TextDocument;
+15 -15
View File
@@ -8,6 +8,10 @@
#include <eepp/system/log.hpp>
#include <eepp/system/packmanager.hpp>
#define MAX_KEYNAME 128
#define MAX_VALUENAME 128
#define MAX_VALUEDATA 2048
namespace EE { namespace System {
IniFile::IniFile( const std::string& iniPath, bool autoLoad ) {
@@ -159,51 +163,47 @@ bool IniFile::readFile() {
}
bool IniFile::writeFile() {
unsigned commentID, keyID, valueID;
IOStreamFile f( mPath, "w" );
return writeStream( f );
}
if ( !f.isOpen() )
bool IniFile::writeStream( IOStream& stream ) {
if ( !stream.isOpen() )
return false;
unsigned commentID, keyID, valueID;
std::string str;
// Write header mComments.
for ( commentID = 0; commentID < mComments.size(); ++commentID ) {
str = ';' + mComments[commentID] + '\n';
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
}
if ( mComments.size() ) {
str = "\n";
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
}
// Write Keys and values.
for ( keyID = 0; keyID < mKeys.size(); ++keyID ) {
str = '[' + mNames[keyID] + ']' + '\n';
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
// Comments.
for ( commentID = 0; commentID < mKeys[keyID].comments.size(); ++commentID ) {
str = ';' + mKeys[keyID].comments[commentID] + '\n';
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
}
// Values.
for ( valueID = 0; valueID < mKeys[keyID].names.size(); ++valueID ) {
str = mKeys[keyID].names[valueID] + '=' + mKeys[keyID].values[valueID] + '\n';
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
}
str = "\n";
f.write( str.c_str(), str.size() );
stream.write( str.c_str(), str.size() );
}
return true;
+29 -11
View File
@@ -458,10 +458,7 @@ bool TextDocument::isBOM() const {
}
void TextDocument::notifyDocumentMoved( const std::string& path ) {
mFilePath = path;
mFileURI = URI( "file://" + mFilePath );
mFileRealPath = FileInfo::isLink( mFilePath ) ? FileInfo( FileInfo( mFilePath ).linksTo() )
: FileInfo( mFilePath );
changeFilePath( path, false );
notifyDocumentMoved();
}
@@ -503,19 +500,14 @@ TextDocument::LoadStatus TextDocument::loadFromFile( const std::string& path ) {
std::string pathFix( path );
Pack* pack = PackManager::instance()->exists( pathFix );
if ( NULL != pack ) {
mFilePath = pathFix;
mFileRealPath = FileInfo();
mFileURI = URI( "file://" + mFilePath );
changeFilePath( pathFix, false );
return loadFromPack( pack, pathFix );
}
}
IOStreamFile file( path, "rb" );
auto ret = loadFromStream( file, path, true );
mFilePath = path;
mFileURI = URI( "file://" + mFilePath );
mFileRealPath = FileInfo::isLink( mFilePath ) ? FileInfo( FileInfo( mFilePath ).linksTo() )
: FileInfo( mFilePath );
changeFilePath( path, false );
resetSyntax();
mLoading = false;
if ( !mLoadingAsync )
@@ -3004,6 +2996,32 @@ void TextDocument::setLines( std::vector<TextDocumentLine>&& lines ) {
mLines = std::move( lines );
}
std::string TextDocument::serializeUndoRedo( bool inverted ) {
return mUndoStack.toJSON( inverted );
}
void TextDocument::unserializeUndoRedo( const std::string& jsonString ) {
return mUndoStack.fromJSON( jsonString );
}
void TextDocument::changeFilePath( const std::string& filePath ) {
changeFilePath( filePath, true );
}
void TextDocument::setDirtyUntilSave() {
mCleanChangeId = std::numeric_limits<Uint64>::max();
notifySelectionChanged();
}
void TextDocument::changeFilePath( const std::string& filePath, bool notify ) {
mFilePath = filePath;
mFileURI = URI( "file://" + mFilePath );
mFileRealPath = FileInfo::isLink( mFilePath ) ? FileInfo( FileInfo( mFilePath ).linksTo() )
: FileInfo( mFilePath );
if ( notify )
notifyDocumentMoved();
}
static inline void changeDepth( SyntaxHighlighter* highlighter, int& depth, const TextPosition& pos,
int dir ) {
if ( highlighter ) {
+172
View File
@@ -1,11 +1,132 @@
#include <eepp/core/core.hpp>
#include <eepp/system/log.hpp>
#include <eepp/ui/doc/textdocument.hpp>
#include <eepp/ui/doc/textundostack.hpp>
#include <nlohmann/json.hpp>
using namespace EE::System;
namespace EE { namespace UI { namespace Doc {
using json = nlohmann::json;
class TextUndoCommand {
public:
TextUndoCommand( const Uint64& id, const TextUndoCommandType& type, const Time& timestamp );
virtual ~TextUndoCommand();
const Uint64& getId() const;
const TextUndoCommandType& getType() const;
const Time& getTimestamp() const;
virtual json toJSON() = 0;
protected:
Uint64 mId;
TextUndoCommandType mType;
Time mTimestamp;
json baseJSON() {
json j;
j["type"] = mType;
j["timestamp"] = mTimestamp.toString();
return j;
}
};
class TextUndoCommandInsert : public TextUndoCommand {
public:
TextUndoCommandInsert( const Uint64& id, const size_t& cursorIdx, const String& text,
const TextPosition& position, const Time& timestamp );
const String& getText() const;
const TextPosition& getPosition() const;
size_t getCursorIdx() const;
json toJSON() {
auto j = baseJSON();
j["text"] = mText.toUtf8();
j["position"] = mPosition.toString();
j["cursorIdx"] = mCursorIdx;
return j;
}
static TextUndoCommandInsert* fromJSON( json j, Uint64 id ) {
auto timestamp = Time::fromString( j["timestamp"].get<std::string>() );
auto text = String::fromUtf8( j["text"].get<std::string>() );
auto position = TextPosition::fromString( j["position"].get<std::string>() );
auto cursorIdx = j["cursorIdx"].get<size_t>();
return eeNew( TextUndoCommandInsert, ( id, cursorIdx, text, position, timestamp ) );
}
protected:
String mText;
TextPosition mPosition;
size_t mCursorIdx;
};
class TextUndoCommandRemove : public TextUndoCommand {
public:
TextUndoCommandRemove( const Uint64& id, const size_t& cursorIdx, const TextRange& range,
const Time& timestamp );
const TextRange& getRange() const;
size_t getCursorIdx() const;
json toJSON() {
auto j = baseJSON();
j["range"] = mRange.toString();
j["cursorIdx"] = mCursorIdx;
return j;
}
static TextUndoCommandRemove* fromJSON( json j, Uint64 id ) {
auto timestamp = Time::fromString( j["timestamp"].get<std::string>() );
auto range = TextRange::fromString( j["range"].get<std::string>() );
auto cursorIdx = j["cursorIdx"].get<size_t>();
return eeNew( TextUndoCommandRemove, ( id, cursorIdx, range, timestamp ) );
}
protected:
TextRange mRange;
size_t mCursorIdx;
};
class TextUndoCommandSelection : public TextUndoCommand {
public:
TextUndoCommandSelection( const Uint64& id, const size_t& cursorIdx,
const TextRanges& selection, const Time& timestamp );
const TextRanges& getSelection() const;
size_t getCursorIdx() const;
json toJSON() {
auto j = baseJSON();
j["range"] = mSelection.toString();
j["cursorIdx"] = mCursorIdx;
return j;
}
static TextUndoCommandSelection* fromJSON( json j, Uint64 id ) {
auto timestamp = Time::fromString( j["timestamp"].get<std::string>() );
auto range = TextRange::fromString( j["range"].get<std::string>() );
auto cursorIdx = j["cursorIdx"].get<size_t>();
return eeNew( TextUndoCommandSelection, ( id, cursorIdx, range, timestamp ) );
}
protected:
TextRanges mSelection;
size_t mCursorIdx;
};
TextUndoCommand::TextUndoCommand( const Uint64& id, const TextUndoCommandType& type,
const Time& timestamp ) :
mId( id ), mType( type ), mTimestamp( timestamp ) {}
@@ -200,6 +321,57 @@ Uint64 TextUndoStack::getCurrentChangeId() const {
return mUndoStack.back()->getId();
}
std::string TextUndoStack::toJSON( bool inverted ) {
json j = json::array();
if ( inverted ) {
while ( hasUndo() )
undo();
for ( auto it = mRedoStack.rbegin(); it != mRedoStack.rend(); it++ ) {
auto cmd = *it;
j.push_back( cmd->toJSON() );
}
while ( hasRedo() )
redo();
} else {
for ( auto it = mUndoStack.rbegin(); it != mUndoStack.rend(); it++ ) {
auto cmd = *it;
j.push_back( cmd->toJSON() );
}
}
return j.dump();
}
void TextUndoStack::fromJSON( const std::string& jsonString ) {
json j;
try {
j = json::parse( jsonString, nullptr, true, true );
if ( !j.is_array() )
return;
for ( auto it = j.rbegin(); it != j.rend(); it++ ) {
const auto& jobj = *it;
auto type = static_cast<TextUndoCommandType>( jobj["type"].get<int>() );
switch ( type ) {
case TextUndoCommandType::Insert:
pushUndo( mRedoStack,
TextUndoCommandInsert::fromJSON( jobj, ++mChangeIdCounter ) );
break;
case TextUndoCommandType::Remove:
pushUndo( mRedoStack,
TextUndoCommandRemove::fromJSON( jobj, ++mChangeIdCounter ) );
break;
case TextUndoCommandType::Selection:
pushUndo( mRedoStack,
TextUndoCommandSelection::fromJSON( jobj, ++mChangeIdCounter ) );
break;
}
}
} catch ( const json::exception& e ) {
Log::error( "TextUndoStack::fromJSON - Error parsing json string:\n%s", jsonString );
}
}
UndoStackContainer& TextUndoStack::getUndoStackContainer() {
return mUndoStack;
}
+155 -15
View File
@@ -175,6 +175,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
workspace.restoreLastSession = ini.getValueB( "workspace", "restore_last_session", false );
workspace.checkForUpdatesAtStartup =
ini.getValueB( "workspace", "check_for_updates_at_startup", true );
workspace.autoSave = ini.getValueB( "workspace", "auto_save", true );
std::map<std::string, bool> pluginsEnabled;
const auto& creators = pluginManager->getDefinitions();
@@ -314,6 +315,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "workspace", "restore_last_session", workspace.restoreLastSession );
ini.setValueB( "workspace", "check_for_updates_at_startup",
workspace.checkForUpdatesAtStartup );
ini.setValueB( "workspace", "auto_save", workspace.autoSave );
const auto& pluginsEnabled = pluginManager->getPluginsEnabled();
for ( const auto& plugin : pluginsEnabled )
@@ -398,7 +400,8 @@ json saveNode( Node* node ) {
void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter,
const std::string& configPath, const ProjectDocumentConfig& docConfig,
const ProjectBuildConfiguration& buildConfig ) {
const ProjectBuildConfiguration& buildConfig, bool onlyIfNeeded,
bool autoSave ) {
FileSystem::dirAddSlashAtEnd( projectFolder );
std::string projectsPath( configPath + "projects" + FileSystem::getOSSlash() );
if ( !FileSystem::fileExists( projectsPath ) )
@@ -426,7 +429,67 @@ void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* ed
cfg.setValue( "nodes", "documents",
saveNode( editorSplitter->getBaseLayout()->getFirstChild() ).dump() );
cfg.deleteKey( "files" );
cfg.writeFile();
if ( onlyIfNeeded ) {
IOStreamString stringFile;
cfg.writeStream( stringFile );
if ( !FileSystem::fileExists( cfg.path() ) ||
MD5::fromString( stringFile.getStream() ) != MD5::fromFile( cfg.path() ) ) {
FileSystem::fileWrite( cfg.path(), stringFile.getStream() );
}
} else {
cfg.writeFile();
}
if ( !autoSave )
return;
std::string statePath( projectsPath + "state" );
if ( !FileSystem::fileExists( statePath ) && !FileSystem::makeDir( statePath ) )
return;
std::string projectStatePath( statePath + FileSystem::getOSSlash() + hash.toHexString() );
if ( !FileSystem::fileExists( projectStatePath ) && !FileSystem::makeDir( projectStatePath ) )
return;
FileSystem::dirAddSlashAtEnd( projectStatePath );
nlohmann::json j = nlohmann::json::array();
std::vector<std::string> fileNames;
editorSplitter->forEachDocSharedPtr(
[&j, &projectStatePath, &fileNames]( std::shared_ptr<TextDocument> doc ) {
if ( !doc->isDirty() )
return;
nlohmann::json fj;
IOStreamString stream;
doc->save( stream, true );
std::string hash = MD5::fromString( stream.getStream() ).toHexString();
std::string cacheFileName = hash + "." + doc->getFilename();
std::string cachePath = projectStatePath + cacheFileName;
fj["cachepath"] = cachePath;
if ( doc->hasFilepath() ) {
fj["fspath"] = doc->getFilePath();
fj["fsmtime"] = FileInfo( doc->getFilePath() ).getModificationTime();
fj["fshash"] = doc->getHashHexString();
} else {
fj["name"] = doc->getFilename();
fj["selection"] = doc->getSelections().toString();
}
j.push_back( std::move( fj ) );
fileNames.push_back( cacheFileName );
if ( !FileSystem::fileExists( cachePath ) ||
MD5::fromFile( cachePath ) != MD5::fromString( stream.getStream() ) ) {
FileSystem::fileWrite( cachePath, stream.getStream() );
}
} );
std::string stateFileName( "state.json" );
fileNames.push_back( stateFileName );
std::string projectStateFilePath( projectStatePath + stateFileName );
if ( j.size() != 0 ) {
std::string stateString( j.dump( 2 ) );
if ( MD5::fromFile( projectStateFilePath ) != MD5::fromString( stateString ) )
FileSystem::fileWrite( projectStateFilePath, stateString );
} else if ( FileSystem::fileExists( projectStateFilePath ) ) {
FileSystem::fileRemove( projectStateFilePath );
}
auto curFiles = FileSystem::filesGetInPath( projectStatePath );
for ( const auto& file : curFiles )
if ( std::find( fileNames.begin(), fileNames.end(), file ) == fileNames.end() )
FileSystem::fileRemove( projectStatePath + file );
}
static void countTotalEditors( json j, size_t& curTotal ) {
@@ -460,7 +523,8 @@ void AppConfig::editorLoadedCounter( ecode::App* app ) {
}
void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
UITabWidget* curTabWidget, ecode::App* app ) {
UITabWidget* curTabWidget, ecode::App* app,
const std::vector<AutoSaveFile>& autoSaveFiles ) {
if ( j["type"] == "tabwidget" ) {
Int64 currentPage = j["current_page"];
size_t totalToLoad = j["files"].size();
@@ -491,20 +555,45 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
editorLoadedCounter( app );
} else {
auto autoSaveIt = std::find_if(
autoSaveFiles.begin(), autoSaveFiles.end(),
[&path]( const AutoSaveFile& file ) { return file.fspath == path; } );
std::string loadPath( path );
AutoSaveFile autoSaveFile;
if ( autoSaveIt != autoSaveFiles.end() )
autoSaveFile = *autoSaveIt;
editorSplitter->loadAsyncFileFromPathInNewTab(
path,
[this, curTabWidget, selection, totalToLoad, currentPage,
app]( UICodeEditor* editor, const std::string& ) {
[this, curTabWidget, selection, totalToLoad, currentPage, app, path,
autoSaveFile]( UICodeEditor* editor, const std::string& ) {
if ( !editor->getDocument().getSelection().isValid() ||
editor->getDocument().getSelection() ==
TextRange( { 0, 0 }, { 0, 0 } ) ) {
editor->getDocument().setSelection( selection );
editor->scrollToCursor();
}
if ( curTabWidget->getTabCount() == totalToLoad )
curTabWidget->setTabSelected( eeclamp<Int32>(
currentPage, 0, curTabWidget->getTabCount() - 1 ) );
if ( !autoSaveFile.cachePath.empty() ) {
TextDocument& doc = editor->getDocument();
auto diskFileInfo = doc.getFileInfo();
TextDocument cachedDoc;
cachedDoc.loadFromFile( autoSaveFile.cachePath );
doc.selectAll();
doc.textInput( cachedDoc.getText() );
doc.setSelection( selection );
doc.resetUndoRedo();
doc.setDirtyUntilSave();
editor->scrollToCursor();
if ( diskFileInfo.getModificationTime() > autoSaveFile.fsmtime )
app->createDocDirtyAlert( editor, false );
}
editorLoadedCounter( app );
},
curTabWidget );
@@ -527,9 +616,9 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
if ( nullptr == splitter )
return;
loadDocuments( editorSplitter, j["first"], curTabWidget, app );
loadDocuments( editorSplitter, j["first"], curTabWidget, app, autoSaveFiles );
UITabWidget* tabWidget = splitter->getLastWidget()->asType<UITabWidget>();
loadDocuments( editorSplitter, j["last"], tabWidget, app );
loadDocuments( editorSplitter, j["last"], tabWidget, app, autoSaveFiles );
splitter->setSplitPartition( StyleSheetLength( j["split"] ) );
}
@@ -537,7 +626,7 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter,
const std::string& configPath, ProjectDocumentConfig& docConfig,
ecode::App* app ) {
ecode::App* app, bool autoSave ) {
FileSystem::dirAddSlashAtEnd( projectFolder );
std::string projectsPath( configPath + "projects" + FileSystem::getOSSlash() );
MD5::Result hash = MD5::fromString( projectFolder );
@@ -572,21 +661,72 @@ void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* ed
app->getProjectBuildManager()->setConfig( prjCfg );
}
std::vector<AutoSaveFile> autoSaveFiles;
if ( autoSave ) {
std::string projectStatePath( projectsPath + "state" + FileSystem::getOSSlash() +
hash.toHexString() + FileSystem::getOSSlash() +
"state.json" );
if ( FileSystem::fileExists( projectStatePath ) ) {
std::string stateStr;
FileSystem::fileGet( projectStatePath, stateStr );
json j;
try {
j = json::parse( stateStr );
if ( !j.is_discarded() && j.is_array() ) {
for ( const auto& jobj : j ) {
AutoSaveFile autoSaveFile;
autoSaveFile.cachePath = jobj.value( "cachepath", "" );
if ( autoSaveFile.cachePath.empty() )
continue;
autoSaveFile.fspath = jobj.value( "fspath", "" );
autoSaveFile.fsmtime = jobj.value( "fsmtime", 0 );
autoSaveFile.fshash = jobj.value( "fshash", "" );
autoSaveFile.name = jobj.value( "name", "" );
autoSaveFile.selection = jobj.value( "selection", "" );
autoSaveFiles.emplace_back( std::move( autoSaveFile ) );
}
}
} catch ( const json::exception& e ) {
Log::error( "AppConfig::loadProject: error loading project state: %s", e.what() );
}
}
}
UITabWidget* curTabWidget =
editorSplitter->tabWidgetFromWidget( editorSplitter->getCurWidget() );
if ( cfg.keyValueExists( "nodes", "documents" ) ) {
json j;
try {
j = json::parse( cfg.getValue( "nodes", "documents" ) );
} catch ( const json::exception& e ) {
Log::error( "AppConfig::loadProject: error loading project: %s", e.what() );
return;
}
if ( j.is_discarded() )
return;
if ( !j.is_discarded() ) {
editorsToLoad = countTotalEditors( j );
loadDocuments( editorSplitter, j, curTabWidget, app, autoSaveFiles );
}
}
editorsToLoad = countTotalEditors( j );
UITabWidget* curTabWidget =
editorSplitter->tabWidgetFromWidget( editorSplitter->getCurWidget() );
loadDocuments( editorSplitter, j, curTabWidget, app );
for ( const auto& autoSaveFile : autoSaveFiles ) {
if ( !autoSaveFile.fspath.empty() || autoSaveFile.name.empty() ||
autoSaveFile.cachePath.empty() )
continue;
editorSplitter->loadAsyncFileFromPathInNewTab(
autoSaveFile.cachePath,
[autoSaveFile]( UICodeEditor* editor, const std::string& ) {
TextDocument& doc = editor->getDocument();
auto selection = TextRange::fromString( autoSaveFile.selection );
doc.setDefaultFileName( autoSaveFile.name );
doc.changeFilePath( autoSaveFile.name );
doc.setDirtyUntilSave();
doc.setSelection( selection );
doc.resetUndoRedo();
doc.setDirtyUntilSave();
editor->scrollToCursor();
},
curTabWidget );
}
}
+14 -4
View File
@@ -122,7 +122,6 @@ struct ProjectDocumentConfig {
};
struct ProjectBuildConfiguration {
ProjectBuildConfiguration() {}
std::string buildName;
std::string buildType;
std::string runName;
@@ -166,12 +165,22 @@ struct TerminalConfig {
struct WorkspaceConfig {
bool restoreLastSession{ false };
bool checkForUpdatesAtStartup{ true };
bool autoSave{ true };
};
struct LanguagesExtensions {
std::map<std::string, std::string> priorities;
};
struct AutoSaveFile {
std::string cachePath;
std::string fspath;
Uint64 fsmtime{ 0 };
std::string fshash;
std::string name;
std::string selection;
};
class AppConfig {
public:
WindowStateConfig windowState;
@@ -203,17 +212,18 @@ class AppConfig {
void saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter,
const std::string& configPath, const ProjectDocumentConfig& docConfig,
const ProjectBuildConfiguration& buildConfig );
const ProjectBuildConfiguration& buildConfig, bool onlyIfNeeded,
bool autoSave );
void loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter,
const std::string& configPath, ProjectDocumentConfig& docConfig,
ecode::App* app );
ecode::App* app, bool autoSave );
protected:
Int64 editorsToLoad{ 0 };
void loadDocuments( UICodeEditorSplitter* editorSplitter, json j, UITabWidget* curTabWidget,
ecode::App* app );
ecode::App* app, const std::vector<AutoSaveFile>& autoSaveFiles );
void editorLoadedCounter( ecode::App* app );
};
+18 -9
View File
@@ -54,7 +54,7 @@ void appLoop() {
}
bool App::onCloseRequestCallback( EE::Window::Window* ) {
if ( mSplitter->isAnyEditorDirty() ) {
if ( mSplitter->isAnyEditorDirty() && !mConfig.workspace.autoSave ) {
if ( mCloseMsgBox )
return false;
mCloseMsgBox = UIMessageBox::New(
@@ -2200,11 +2200,12 @@ bool App::isUnlockedCommand( const std::string& command ) {
return std::find( cmds.begin(), cmds.end(), command ) != cmds.end();
}
void App::saveProject() {
void App::saveProject( bool onlyIfNeeded, bool autoSaveEnabled ) {
if ( !mCurrentProject.empty() ) {
mConfig.saveProject( mCurrentProject, mSplitter, mConfigPath, mProjectDocConfig,
mProjectBuildManager ? mProjectBuildManager->getConfig()
: ProjectBuildConfiguration() );
: ProjectBuildConfiguration(),
onlyIfNeeded, autoSaveEnabled && mConfig.workspace.autoSave );
}
}
@@ -2258,10 +2259,12 @@ void App::closeFolder() {
if ( mCurrentProject.empty() )
return;
saveProject( true );
if ( mProjectBuildManager )
mProjectBuildManager.reset();
if ( mSplitter->isAnyEditorDirty() ) {
if ( mSplitter->isAnyEditorDirty() && !mConfig.workspace.autoSave ) {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
i18n( "confirm_close_folder",
@@ -2284,7 +2287,7 @@ void App::closeFolder() {
}
}
void App::createDocDirtyAlert( UICodeEditor* editor ) {
void App::createDocDirtyAlert( UICodeEditor* editor, bool showEnableAutoReload ) {
UILinearLayout* docAlert = editor->findByClass<UILinearLayout>( "doc_alert" );
if ( docAlert )
@@ -2310,7 +2313,7 @@ void App::createDocDirtyAlert( UICodeEditor* editor ) {
editor->enableReportSizeChangeToChilds();
docAlert->find( "file_autoreload" )
->setVisible( !editor->getDocument().isDirty() )
->setVisible( showEnableAutoReload ? !editor->getDocument().isDirty() : false )
->onClick( [editor, docAlert, this]( const MouseEvent* ) {
editor->getDocument().reload();
editor->disableReportSizeChangeToChilds();
@@ -2688,6 +2691,11 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
UICodeEditor* editor = event->getNode()->asType<UICodeEditor>();
editor->runOnMainThread( [this, editor] {
updateEditorTabTitle( editor );
UITab* tab = reinterpret_cast<UITab*>( editor->getData() );
tab->setTooltipText(
editor->getDocument().hasFilepath() ? editor->getDocument().getFilePath() : "" );
editor->setSyntaxDefinition( editor->getDocument().guessSyntax() );
} );
} );
@@ -3345,7 +3353,8 @@ void App::loadFolder( const std::string& path ) {
Clock projClock;
mProjectBuildManager =
std::make_unique<ProjectBuildManager>( rpath, mThreadPool, mSidePanel, this );
mConfig.loadProject( rpath, mSplitter, mConfigPath, mProjectDocConfig, this );
mConfig.loadProject( rpath, mSplitter, mConfigPath, mProjectDocConfig, this,
mConfig.workspace.autoSave );
Log::info( "Load project took: %.2f ms", projClock.getElapsedTime().asMilliseconds() );
loadFileSystemMatcher( rpath );
@@ -3379,7 +3388,7 @@ void App::loadFolder( const std::string& path ) {
mPluginManager->setWorkspaceFolder( rpath );
saveProject();
saveProject( true, false );
}
#if EE_PLATFORM == EE_PLATFORM_MACOS
@@ -3895,7 +3904,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe
if ( mWindow && mThreadPool &&
mWindow->getInput()->getElapsedSinceLastKeyboardOrMouseEvent().asSeconds() <
60.f ) {
saveProject();
saveProject( true );
#if EE_PLATFORM == EE_PLATFORM_LINUX
mThreadPool->run( [] { malloc_trim( 0 ); } );
#endif
+3 -3
View File
@@ -457,7 +457,7 @@ class App : public UICodeEditorSplitter::Client {
const std::string& getFileToOpen() const;
void saveProject();
void saveProject( bool onlyIfNeeded = false, bool autoSaveEnabled = true );
std::pair<bool, std::string> generateConfigPath();
@@ -465,6 +465,8 @@ class App : public UICodeEditorSplitter::Client {
bool isAnyStatusBarSectionVisible() const;
void createDocDirtyAlert( UICodeEditor* editor, bool showEnableAutoReload = true );
protected:
std::vector<std::string> mArgs;
EE::Window::Window* mWindow{ nullptr };
@@ -623,8 +625,6 @@ class App : public UICodeEditorSplitter::Client {
void removeFolderWatches();
void createDocDirtyAlert( UICodeEditor* editor );
void createDocManyLangsAlert( UICodeEditor* editor );
void syncProjectTreeWithEditor( UICodeEditor* editor );
+12
View File
@@ -640,6 +640,16 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->getConfig().editor.autoReloadOnDiskChange )
->setId( "autoreload_on_disk_change" );
mGlobalMenu
->addCheckBox( i18n( "auto_save_documents", "Auto-Save Open Documents" ),
mApp->getConfig().workspace.autoSave )
->setTooltipText(
i18n( "auto_save_documents_desc",
"When auto-save open documents is enabled the editor will keep\n"
"the document buffer changes between sessions, even if they are not saved\n"
"before exiting the program." ) )
->setId( "auto_save_documents" );
mGlobalMenu->addSeparator();
mGlobalMenu->add( i18n( "line_breaking_column", "Line Breaking Column" ) )
@@ -668,6 +678,8 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->getConfig().doc.autoDetectIndentType = item->isActive();
} else if ( "autoreload_on_disk_change" == id ) {
mApp->getConfig().editor.autoReloadOnDiskChange = item->isActive();
} else if ( "auto_save_documents" == id ) {
mApp->getConfig().workspace.autoSave = item->isActive();
}
} else if ( "line_breaking_column" == id ) {
mApp->setLineBreakingColumn();