TextDocument: Added support for CR line endings (Classic Macintosh). Fixes in save and load.
ecode:
Closes SpartanJ/ecode#26 (a consequence of issue #27).
Closes SpartanJ/ecode#27 (mixed CRLF and LF broke files, pasting text with CRLF line endings broke files).
Improved SpartanJ/ecode#24.
This commit is contained in:
Martín Lucas Golini
2023-02-02 19:24:48 -03:00
parent ea45fe7180
commit 9a9b45ecb2
9 changed files with 142 additions and 42 deletions

View File

@@ -829,6 +829,9 @@ class EE_API String {
String getFirstLine();
/** Replace all occurrences of the search string with the replacement string. */
void replaceAll( const String& that, const String& with );
private:
friend EE_API bool operator==( const String& left, const String& right );
friend EE_API bool operator<( const String& left, const String& right );

View File

@@ -39,12 +39,32 @@ class EE_API TextDocument {
enum class IndentType { IndentSpaces, IndentTabs };
enum class LineEnding { LF, CRLF };
enum class LineEnding { LF, CRLF, CR };
enum class FindReplaceType { Normal, LuaPattern };
enum class LoadStatus { Loaded, Interrupted, Failed };
static std::string lineEndingToString( const LineEnding& le ) {
switch ( le ) {
case LineEnding::CRLF:
return "CRLF";
case LineEnding::CR:
return "CR";
case LineEnding::LF:
default:
return "LF";
}
}
static LineEnding stringToLineEnding( const std::string& str ) {
if ( "CR" == str )
return LineEnding::CR;
if ( "CRLF" == str )
return LineEnding::CRLF;
return LineEnding::LF;
}
class EE_API Client {
public:
virtual ~Client();

View File

@@ -707,6 +707,10 @@ void String::replaceAll( String& target, const String& that, const String& with
}
}
void String::replaceAll( const String& that, const String& with ) {
String::replaceAll( *this, that, with );
}
void String::replace( std::string& target, const std::string& that, const std::string& with ) {
std::size_t start_pos = target.find( that );
if ( start_pos == std::string::npos )

View File

@@ -82,10 +82,13 @@ void TextDocument::resetCursor() {
static String ptrGetLine( char* data, const size_t& size, size_t& position ) {
position = 0;
while ( position < size && data[position] != '\n' )
while ( position < size && data[position] != '\n' && data[position] != '\r' )
position++;
if ( position < size )
if ( position < size ) {
if ( position + 1 < size && data[position] == '\r' && data[position + 1] == '\n' )
position++;
position++;
}
return String( data, position );
}
@@ -132,17 +135,24 @@ TextDocument::LoadStatus TextDocument::loadFromStream( IOStream& file, std::stri
bufferPtr += position;
consume -= position;
size_t lineBufferSize = lineBuffer.size();
char lastChar = lineBuffer[lineBufferSize - 1];
if ( lineBuffer[lineBufferSize - 1] == '\n' || !consume ) {
if ( mLines.empty() && lineBufferSize > 1 &&
lineBuffer[lineBufferSize - 2] == '\r' ) {
mLineEnding = LineEnding::CRLF;
if ( lastChar == '\n' || lastChar == '\r' || !consume ) {
if ( mLines.empty() ) {
if ( lineBufferSize > 1 && lineBuffer[lineBufferSize - 2] == '\r' &&
lastChar == '\n' ) {
mLineEnding = LineEnding::CRLF;
} else if ( lastChar == '\r' ) {
mLineEnding = LineEnding::CR;
}
}
if ( mLineEnding == LineEnding::CRLF && lineBufferSize > 1 &&
lineBuffer[lineBufferSize - 1] == '\n' ) {
lastChar == '\n' ) {
lineBuffer[lineBuffer.size() - 2] = '\n';
lineBuffer.resize( lineBufferSize - 1 );
} else if ( mLineEnding == LineEnding::CR && lineBufferSize > 0 ) {
lineBuffer[lineBuffer.size() - 1] = '\n';
}
mLines.push_back( lineBuffer );
@@ -544,6 +554,9 @@ bool TextDocument::save( IOStream& stream, bool keepUndoRedoStatus ) {
text += "\n";
}
stream.write( text.c_str(), text.size() );
} else if ( mLineEnding == LineEnding::CR ) {
text[text.size() - 1] = '\r';
stream.write( text.c_str(), text.size() );
} else {
stream.write( text.c_str(), text.size() );
}
@@ -1304,10 +1317,23 @@ void TextDocument::textInput( const String& text ) {
}
}
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() )
deleteTo( i, 0 );
setSelection( i, insert( i, getSelectionIndex( i ).start(), text ) );
auto crPOS = text.find_first_of( '\r' );
if ( crPOS != String::InvalidPos ) {
String textCpy( text );
textCpy.replaceAll( "\r", "" );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() )
deleteTo( i, 0 );
setSelection( i, insert( i, getSelectionIndex( i ).start(), textCpy ) );
}
} else {
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() )
deleteTo( i, 0 );
setSelection( i, insert( i, getSelectionIndex( i ).start(), text ) );
}
}
}

View File

@@ -103,7 +103,15 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
doc.writeUnicodeBOM = ini.getValueB( "document", "write_bom", false );
doc.indentWidth = ini.getValueI( "document", "indent_width", 4 );
doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false );
doc.windowsLineEndings = ini.getValueB( "document", "windows_line_endings", false );
doc.lineEndings =
TextDocument::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) );
// Migrate old data
if ( ini.keyValueExists( "document", "windows_line_endings" ) &&
!ini.keyValueExists( "document", "line_endings" ) &&
ini.getValueB( "document", "windows_line_endings" ) == true ) {
doc.lineEndings = TextDocument::LineEnding::CRLF;
}
doc.tabWidth = eemax( 2, ini.getValueI( "document", "tab_width", 4 ) );
doc.lineBreakingColumn = eemax( 0, ini.getValueI( "document", "line_breaking_column", 100 ) );
editor.autoCloseBrackets = ini.getValue( "editor", "auto_close_brackets", "" );
@@ -211,7 +219,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "document", "write_bom", doc.writeUnicodeBOM );
ini.setValueI( "document", "indent_width", doc.indentWidth );
ini.setValueB( "document", "indent_spaces", doc.indentSpaces );
ini.setValueB( "document", "windows_line_endings", doc.windowsLineEndings );
ini.setValue( "document", "line_endings", TextDocument::lineEndingToString( doc.lineEndings ) );
ini.setValueI( "document", "tab_width", doc.tabWidth );
ini.setValueI( "document", "line_breaking_column", doc.lineBreakingColumn );
ini.setValue( "editor", "auto_close_brackets", editor.autoCloseBrackets );
@@ -348,7 +356,8 @@ void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* ed
ini.setValueB( "document", "write_bom", docConfig.doc.writeUnicodeBOM );
ini.setValueI( "document", "indent_width", docConfig.doc.indentWidth );
ini.setValueB( "document", "indent_spaces", docConfig.doc.indentSpaces );
ini.setValueB( "document", "windows_line_endings", docConfig.doc.windowsLineEndings );
ini.setValue( "document", "line_endings",
TextDocument::lineEndingToString( docConfig.doc.lineEndings ) );
ini.setValueI( "document", "tab_width", docConfig.doc.tabWidth );
ini.setValueI( "document", "line_breaking_column", docConfig.doc.lineBreakingColumn );
ini.setValue( "nodes", "documents",
@@ -440,7 +449,15 @@ void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* ed
docConfig.doc.writeUnicodeBOM = ini.getValueB( "document", "write_bom", false );
docConfig.doc.indentWidth = ini.getValueI( "document", "indent_width", 4 );
docConfig.doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false );
docConfig.doc.windowsLineEndings = ini.getValueB( "document", "windows_line_endings", false );
docConfig.doc.lineEndings =
TextDocument::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) );
// Migrate old data
if ( ini.keyValueExists( "document", "windows_line_endings" ) &&
!ini.keyValueExists( "document", "line_endings" ) &&
ini.getValueB( "document", "windows_line_endings" ) == true ) {
docConfig.doc.lineEndings = TextDocument::LineEnding::CRLF;
}
docConfig.doc.tabWidth = eemax( 2, ini.getValueI( "document", "tab_width", 4 ) );
docConfig.doc.lineBreakingColumn =
eemax( 0, ini.getValueI( "document", "line_breaking_column", 100 ) );

View File

@@ -74,7 +74,7 @@ struct DocumentConfig {
bool autoDetectIndentType{ true };
bool writeUnicodeBOM{ false };
bool indentSpaces{ false };
bool windowsLineEndings{ false };
TextDocument::LineEnding lineEndings{ TextDocument::LineEnding::LF };
int indentWidth{ 4 };
int tabWidth{ 4 };
int lineBreakingColumn{ 100 };

View File

@@ -186,11 +186,19 @@ void App::openFileDialog() {
dialog->show();
}
std::string App::getLastUsedFolder() {
if ( !mCurrentProject.empty() )
return mCurrentProject;
if ( !mRecentFolders.empty() )
return mRecentFolders.front();
return ".";
}
void App::openFolderDialog() {
UIFileDialog* dialog =
UIFileDialog::New( UIFileDialog::DefaultFlags | UIFileDialog::AllowFolderSelect |
UIFileDialog::ShowOnlyFolders,
"*", !mCurrentProject.empty() ? mCurrentProject : "." );
"*", getLastUsedFolder() );
dialog->setWindowFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL );
dialog->setTitle( i18n( "open_folder", "Open Folder" ) );
dialog->setCloseShortcut( KEY_ESCAPE );
@@ -1242,7 +1250,7 @@ void App::updateDocInfo( TextDocument& doc ) {
doc.getSelection().start().line() + 1, doc.linesCount(),
i18n( "col_abbr", "col" ).toUtf8().c_str(),
mSplitter->getCurEditor()->getCurrentColumnCount(),
doc.getLineEnding() == TextDocument::LineEnding::LF ? "LF" : "CRLF" ) );
TextDocument::lineEndingToString( doc.getLineEnding() ).c_str() ) );
}
}
@@ -1644,8 +1652,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
editor->setCursorBlinkTime( config.cursorBlinkingTime );
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) );
doc.setLineEnding( docc.windowsLineEndings ? TextDocument::LineEnding::CRLF
: TextDocument::LineEnding::LF );
doc.setLineEnding( docc.lineEndings );
doc.setTrimTrailingWhitespaces( docc.trimTrailingWhitespaces );
doc.setForceNewLineAtEndOfFile( docc.forceNewLineAtEndOfFile );
doc.setIndentType( docc.indentSpaces ? TextDocument::IndentType::IndentSpaces

View File

@@ -431,6 +431,8 @@ class App : public UICodeEditorSplitter::Client {
void onPluginEnabled( UICodeEditorPlugin* plugin );
void checkForUpdatesResponse( Http::Response response, bool fromStartup );
std::string getLastUsedFolder();
};
} // namespace ecode

View File

@@ -282,17 +282,25 @@ UIMenu* SettingsMenu::createDocumentMenu() {
} );
UIPopUpMenu* lineEndingsMenu = UIPopUpMenu::New();
lineEndingsMenu->addRadioButton( "Windows (CR/LF)", mApp->getConfig().doc.windowsLineEndings )
->setId( "windows" );
lineEndingsMenu->addRadioButton( "Unix (LF)", !mApp->getConfig().doc.windowsLineEndings )
->setId( "unix" );
lineEndingsMenu
->addRadioButton( "Windows/DOS (CR/LF)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::CRLF )
->setId( "CRLF" );
lineEndingsMenu
->addRadioButton( "Unix (LF)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::LF )
->setId( "LF" );
lineEndingsMenu
->addRadioButton( "Macintosh (CR)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::CR )
->setId( "CR" );
mDocMenu->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsMenu )
->setId( "line_endings_cur" );
lineEndingsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) {
bool winLe = event->getNode()->asType<UIRadioButton>()->getId() == "windows";
auto le =
TextDocument::stringToLineEnding( event->getNode()->asType<UIRadioButton>()->getId() );
if ( mSplitter->curEditorExistsAndFocused() ) {
mSplitter->getCurEditor()->getDocument().setLineEnding(
winLe ? TextDocument::LineEnding::CRLF : TextDocument::LineEnding::LF );
mSplitter->getCurEditor()->getDocument().setLineEnding( le );
mApp->updateDocInfo( mSplitter->getCurEditor()->getDocument() );
}
} );
@@ -394,15 +402,22 @@ UIMenu* SettingsMenu::createDocumentMenu() {
UIPopUpMenu* lineEndingsGlobalMenu = UIPopUpMenu::New();
lineEndingsGlobalMenu
->addRadioButton( "Windows (CR/LF)", mApp->getConfig().doc.windowsLineEndings )
->setId( "windows" );
lineEndingsGlobalMenu->addRadioButton( "Unix (LF)", !mApp->getConfig().doc.windowsLineEndings )
->setId( "unix" );
->addRadioButton( "Windows/DOS (CR/LF)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::CRLF )
->setId( "CRLF" );
lineEndingsGlobalMenu
->addRadioButton( "Unix (LF)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::LF )
->setId( "LF" );
lineEndingsGlobalMenu
->addRadioButton( "Macintosh (CR)",
mApp->getConfig().doc.lineEndings == TextDocument::LineEnding::CR )
->setId( "CR" );
globalMenu->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsGlobalMenu )
->setId( "line_endings" );
lineEndingsGlobalMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) {
bool winLe = event->getNode()->asType<UIRadioButton>()->getId() == "windows";
mApp->getConfig().doc.windowsLineEndings = winLe;
mApp->getConfig().doc.lineEndings =
TextDocument::stringToLineEnding( event->getNode()->asType<UIRadioButton>()->getId() );
} );
UIPopUpMenu* bracketsMenu = UIPopUpMenu::New();
@@ -589,18 +604,24 @@ UIMenu* SettingsMenu::createDocumentMenu() {
UIPopUpMenu* lineEndingsProjectMenu = UIPopUpMenu::New();
lineEndingsProjectMenu
->addRadioButton( "Windows (CR/LF)", mApp->getProjectDocConfig().doc.windowsLineEndings )
->setId( "windows" );
->addRadioButton( "Windows (CR/LF)", mApp->getProjectDocConfig().doc.lineEndings ==
TextDocument::LineEnding::CRLF )
->setId( "CRLF" );
lineEndingsProjectMenu
->addRadioButton( "Unix (LF)", !mApp->getProjectDocConfig().doc.windowsLineEndings )
->setId( "unix" );
->addRadioButton( "Unix (LF)", mApp->getProjectDocConfig().doc.lineEndings ==
TextDocument::LineEnding::LF )
->setId( "LF" );
lineEndingsProjectMenu
->addRadioButton( "Macintosh (CR)", mApp->getProjectDocConfig().doc.lineEndings ==
TextDocument::LineEnding::CR )
->setId( "CR" );
mProjectMenu
->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsProjectMenu )
->setId( "line_endings" )
->setEnabled( !mApp->getProjectDocConfig().useGlobalSettings );
lineEndingsProjectMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) {
bool winLe = event->getNode()->asType<UIRadioButton>()->getId() == "windows";
mApp->getProjectDocConfig().doc.windowsLineEndings = winLe;
mApp->getProjectDocConfig().doc.lineEndings =
TextDocument::stringToLineEnding( event->getNode()->asType<UIRadioButton>()->getId() );
} );
mProjectMenu
@@ -1271,7 +1292,7 @@ void SettingsMenu::updateProjectSettingsMenu() {
mProjectMenu->find( "line_endings" )
->asType<UIMenuSubMenu>()
->getSubMenu()
->find( mApp->getProjectDocConfig().doc.windowsLineEndings ? "windows" : "unix" )
->find( TextDocument::lineEndingToString( mApp->getProjectDocConfig().doc.lineEndings ) )
->asType<UIMenuRadioButton>()
->setActive( true );
@@ -1340,7 +1361,7 @@ void SettingsMenu::updateDocumentMenu() {
mDocMenu->find( "line_endings_cur" )
->asType<UIMenuSubMenu>()
->getSubMenu()
->find( doc.getLineEnding() == TextDocument::LineEnding::CRLF ? "windows" : "unix" )
->find( TextDocument::lineEndingToString( doc.getLineEnding() ) )
->asType<UIMenuRadioButton>()
->setActive( true );