diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index 767bcf720..d11d2d18e 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -965,4 +965,8 @@ Verwendet strftime-Formatbezeichner. Die Dateierweiterung richtet sich nach dem Aus Standard Ein + Tab-Ausgang + Wenn Tab vor einem konfigurierten Zeichen gedrückt wird, springt der Cursor darüber. Gilt nur für einen einzelnen Cursor ohne Auswahl. + Tab-Ausgangszeichen festlegen + Legen Sie die Zeichen fest, über die der Cursor beim Drücken von Tab springen kann. diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index 21e5eee05..d9582ab61 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -949,4 +949,8 @@ Uses strftime format specifiers. The file extension follows the selected screens Off Default On + Tab Out + Pressing Tab before a configured character moves the cursor past it. Only applies to a single cursor without a selection. + Set Tab Out Characters + Set the characters the cursor can move past when pressing Tab. diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index b508b0420..a6815adf3 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -944,4 +944,8 @@ Utilise les spécificateurs de format strftime. L’extension du fichier corresp Désactivé Par défaut Activé + Sortie par tabulation + Appuyer sur Tab avant un caractère configuré déplace le curseur après celui-ci. S'applique uniquement à un curseur sans sélection. + Définir les caractères de sortie + Définissez les caractères que le curseur peut franchir en appuyant sur Tab. diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml index 481214551..9c84db85d 100644 --- a/bin/assets/i18n/zh.xml +++ b/bin/assets/i18n/zh.xml @@ -723,4 +723,8 @@ file in the directory tree. 关闭 默认 开启 + Tab 跳出 + 在已配置的字符前按 Tab 可将光标移至该字符之后。仅适用于无选区的单个光标。 + 设置 Tab 跳出字符 + 设置按 Tab 时光标可以跳过的字符。 diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 33e778ad5..99cedb150 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -564,6 +564,14 @@ class EE_API TextDocument { const std::vector>& autoCloseBracketsPairs ); + bool getTabOutEnabled() const; + + void setTabOutEnabled( bool enabled ); + + const String& getTabOutChars() const; + + void setTabOutChars( const String& chars ); + bool isDirtyOnFileSystem() const; void setDirtyOnFileSystem( bool dirtyOnFileSystem ); @@ -780,6 +788,7 @@ class EE_API TextDocument { bool mTrimTrailingWhitespaces{ false }; bool mVerbose{ false }; bool mAutoCloseBrackets{ false }; + bool mTabOutEnabled{ false }; bool mDirtyOnFileSystem{ false }; bool mSaving{ false }; bool mDeleteOnClose{ false }; @@ -789,6 +798,7 @@ class EE_API TextDocument { bool mDoingTextInput{ false }; bool mInsertingText{ false }; std::vector> mAutoCloseBracketsPairs; + String mTabOutChars{ ")]}'\":;>," }; Uint32 mIndentWidth{ 4 }; IndentType mIndentType{ IndentType::IndentTabs }; AutoIndentConfig mAutoIndent{ AutoIndentConfig::Smart }; diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index b36f60efd..48684abe0 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -2906,6 +2906,12 @@ void TextDocument::removeFromStartOfSelectedLines( const String& text, bool skip } void TextDocument::indent() { + if ( mTabOutEnabled && mSelection.size() == 1 && !hasSelection() && + mTabOutChars.find( getCurrentChar() ) != String::InvalidPos ) { + moveToNextChar(); + return; + } + if ( hasSelection() ) { insertAtStartOfSelectedLines( getIndentString(), false ); } else { @@ -3042,6 +3048,22 @@ void TextDocument::setAutoCloseBracketsPairs( mAutoCloseBracketsPairs = autoCloseBracketsPairs; } +bool TextDocument::getTabOutEnabled() const { + return mTabOutEnabled; +} + +void TextDocument::setTabOutEnabled( bool enabled ) { + mTabOutEnabled = enabled; +} + +const String& TextDocument::getTabOutChars() const { + return mTabOutChars; +} + +void TextDocument::setTabOutChars( const String& chars ) { + mTabOutChars = chars; +} + bool TextDocument::isDirtyOnFileSystem() const { return mDirtyOnFileSystem; } diff --git a/src/tests/unit_tests/textdocument_tests.cpp b/src/tests/unit_tests/textdocument_tests.cpp index a432a2e63..06b36a855 100644 --- a/src/tests/unit_tests/textdocument_tests.cpp +++ b/src/tests/unit_tests/textdocument_tests.cpp @@ -294,6 +294,73 @@ UTEST( TextDocument, newLineNormal ) { EXPECT_STDSTREQ( TextPosition( 1, 2 ).toString(), doc.getSelection().start().toString() ); } +UTEST( TextDocument, indentTabsOutOfTrailingCharactersWithSingleCursor ) { + static constexpr char trailingCharacters[] = ")]}'\":;>,"; + + for ( const char trailingCharacter : trailingCharacters ) { + if ( trailingCharacter == '\0' ) + break; + + TextDocument doc; + doc.setIndentType( TextDocument::IndentType::IndentTabs ); + doc.setTabOutEnabled( true ); + doc.insert( 0, { 0, 0 }, String( trailingCharacter ) ); + doc.setSelection( { 0, 0 } ); + doc.indent(); + + EXPECT_STRINGEQ( String( trailingCharacter ) + "\n", doc.line( 0 ).getText() ); + EXPECT_STDSTREQ( TextPosition( 0, 1 ).toString(), doc.getSelection().start().toString() ); + } +} + +UTEST( TextDocument, indentFallsBackToExistingBehavior ) { + TextDocument doc; + doc.setIndentType( TextDocument::IndentType::IndentTabs ); + doc.setTabOutEnabled( true ); + doc.insert( 0, { 0, 0 }, ") ordinary" ); + + // A non-trailing character still inserts indentation. + doc.setSelection( { 0, 2 } ); + doc.indent(); + EXPECT_STRINGEQ( ") \tordinary\n", doc.line( 0 ).getText() ); + + // A selection still indents the selected line, even before a trailing character. + doc.setSelection( { { 0, 0 }, { 0, 1 } } ); + doc.indent(); + EXPECT_STRINGEQ( "\t) \tordinary\n", doc.line( 0 ).getText() ); +} + +UTEST( TextDocument, indentDoesNotTabOutWithMultipleCursors ) { + TextDocument doc; + doc.setIndentType( TextDocument::IndentType::IndentTabs ); + doc.setTabOutEnabled( true ); + doc.insert( 0, { 0, 0 }, ")\n}" ); + doc.resetSelection( TextRanges( std::vector{ TextRange( { 0, 0 }, { 0, 0 } ), + TextRange( { 1, 0 }, { 1, 0 } ) } ) ); + doc.indent(); + + EXPECT_STRINGEQ( "\t)\n", doc.line( 0 ).getText() ); + EXPECT_STRINGEQ( "\t}\n", doc.line( 1 ).getText() ); +} + +UTEST( TextDocument, indentTabOutIsOptionalAndConfigurable ) { + TextDocument doc; + doc.setIndentType( TextDocument::IndentType::IndentTabs ); + doc.insert( 0, { 0, 0 }, ")x" ); + doc.setSelection( { 0, 0 } ); + + // Disabled by default. + doc.indent(); + EXPECT_STRINGEQ( "\t)x\n", doc.line( 0 ).getText() ); + + // A custom set replaces the defaults. + doc.setTabOutEnabled( true ); + doc.setTabOutChars( "x" ); + doc.setSelection( { 0, 2 } ); + doc.indent(); + EXPECT_STDSTREQ( TextPosition( 0, 3 ).toString(), doc.getSelection().start().toString() ); +} + UTEST( TextDocument, moveToStartOfContent ) { TextDocument doc; doc.insert( 0, { 0, 0 }, " content" ); diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 3b8a841a6..3740ef2dc 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -184,6 +184,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, doc.indentWidth = ini.getValueI( "document", "indent_width", 4 ); doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false ); doc.tabStops = ini.getValueB( "document", "tab_stops", true ); + doc.tabOutEnabled = ini.getValueB( "document", "tab_out_enabled", false ); + doc.tabOutChars = ini.getValue( "document", "tab_out_chars", ")]}'\":;>," ); doc.lineEndings = TextFormat::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) ); editor.newTabPosition = @@ -395,6 +397,8 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "document", "write_bom", doc.writeUnicodeBOM ); ini.setValueI( "document", "indent_width", doc.indentWidth ); ini.setValueB( "document", "tab_stops", doc.tabStops ); + ini.setValueB( "document", "tab_out_enabled", doc.tabOutEnabled ); + ini.setValue( "document", "tab_out_chars", doc.tabOutChars ); ini.setValueB( "document", "indent_spaces", doc.indentSpaces ); ini.setValue( "document", "line_endings", TextFormat::lineEndingToString( doc.lineEndings ) ); ini.setValueI( "document", "tab_width", doc.tabWidth ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 408d3ab69..a222b061a 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -156,8 +156,10 @@ struct DocumentConfig { bool writeUnicodeBOM{ false }; bool indentSpaces{ false }; bool tabStops{ true }; + bool tabOutEnabled{ false }; TextFormat::LineEnding lineEndings{ TextFormat::LineEnding::LF }; TextDocument::AutoIndentConfig autoIndent{ TextDocument::AutoIndentConfig::Smart }; + std::string tabOutChars{ ")]}'\":;>," }; int indentWidth{ 4 }; int tabWidth{ 4 }; int lineBreakingColumn{ 100 }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 5fba64f0d..7f1d8b2b6 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2985,6 +2985,8 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() ); doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) ); + doc.setTabOutEnabled( mConfig.doc.tabOutEnabled ); + doc.setTabOutChars( String::fromUtf8( mConfig.doc.tabOutChars ) ); doc.setLineEnding( docc.lineEndings ); doc.setTrimTrailingWhitespaces( docc.trimTrailingWhitespaces ); doc.setForceNewLineAtEndOfFile( docc.forceNewLineAtEndOfFile ); diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp index 7965036e3..70eb718b5 100644 --- a/src/tools/ecode/settingsactions.cpp +++ b/src/tools/ecode/settingsactions.cpp @@ -251,6 +251,25 @@ void SettingsActions::setIndentTabCharacter() { mApp->setFocusEditorOnClose( msgBox ); } +void SettingsActions::setTabOutChars() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_tab_out_characters_message", + "Set the characters the cursor can move past when pressing Tab." ) ); + msgBox->setTitle( i18n( "set_tab_out_characters", "Set Tab Out Characters" ) ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setText( String::fromUtf8( mApp->getConfig().doc.tabOutChars ) ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + String chars = msgBox->getTextInput()->getText(); + mApp->getConfig().doc.tabOutChars = chars.toUtf8(); + mApp->getSplitter()->forEachEditor( + [&chars]( UICodeEditor* editor ) { editor->getDocument().setTabOutChars( chars ); } ); + msgBox->closeWindow(); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + void SettingsActions::setFoldRefreshFreq() { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, diff --git a/src/tools/ecode/settingsactions.hpp b/src/tools/ecode/settingsactions.hpp index 38c04c98c..70068b271 100644 --- a/src/tools/ecode/settingsactions.hpp +++ b/src/tools/ecode/settingsactions.hpp @@ -25,6 +25,8 @@ class SettingsActions { void setIndentTabCharacter(); + void setTabOutChars(); + void setFoldRefreshFreq(); void setUIScaleFactor(); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 08045ed41..8102b6a5a 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -824,6 +824,14 @@ UIMenu* SettingsMenu::createDocumentMenu() { "like a fixed number of spaces regardless of their position." ) ) ->setId( "tab_stops" ); + mGlobalMenu->addCheckBox( i18n( "tab_out", "Tab Out" ), mApp->getConfig().doc.tabOutEnabled ) + ->setTooltipText( i18n( + "tab_out_desc", "Pressing Tab before a configured character moves the cursor past it. " + "Only applies to a single cursor without a selection." ) ) + ->setId( "tab_out" ); + mGlobalMenu->add( i18n( "set_tab_out_characters", "Set Tab Out Characters" ) ) + ->setId( "set_tab_out_characters" ); + mGlobalMenu ->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ), mApp->getConfig().doc.forceNewLineAtEndOfFile ) @@ -948,6 +956,11 @@ UIMenu* SettingsMenu::createDocumentMenu() { mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { editor->setTabStops( mApp->getConfig().doc.tabStops ); } ); + } else if ( "tab_out" == id ) { + mApp->getConfig().doc.tabOutEnabled = item->isActive(); + mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { + editor->getDocument().setTabOutEnabled( mApp->getConfig().doc.tabOutEnabled ); + } ); } } else if ( "line_breaking_column" == id ) { mApp->getSettingsActions()->setLineBreakingColumn(); @@ -957,6 +970,8 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getSettingsActions()->setCursorBlinkingTime(); } else if ( "indent_tab_character" == id ) { mApp->getSettingsActions()->setIndentTabCharacter(); + } else if ( "set_tab_out_characters" == id ) { + mApp->getSettingsActions()->setTabOutChars(); } } );