mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-18 06:55:48 +03:00
eepp:
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:
@@ -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 );
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user