diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index db3cbb6bd..a6649d8ee 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -2214,27 +2214,67 @@ std::vector TextDocument::autoCloseBrackets( const String& text ) { continue; } - if ( isClose && !isSame ) + if ( isClose && !isSame ) { + mustClose = false; + } else if ( !isClose && !isNonWord( ch ) ) { + mustClose = false; + } + } + + if ( mustClose && isSame ) { + Int64 left = sel.start().column() - 1; + Int64 right = sel.start().column(); + const String& lineText = line( sel.start().line() ).getText(); + Int64 len = lineText.size(); + Int64 limitLeft = eemax( 0ll, sel.start().column() - 512 ); + Int64 limitRight = eemin( len, sel.start().column() + 512 ); + int unclosedQuotes = 0; + while ( left >= limitLeft || right < limitRight ) { + bool matchLeft = left >= limitLeft && lineText[left] == text[0]; + bool matchRight = right < limitRight && lineText[right] == text[0]; + if ( matchLeft && matchRight ) { + left--; + right++; + } else if ( matchLeft ) { + unclosedQuotes++; + left--; + } else if ( matchRight ) { + unclosedQuotes++; + right++; + } else { + if ( left >= limitLeft ) + left--; + if ( right < limitRight ) + right++; + } + } + if ( unclosedQuotes % 2 != 0 ) + mustClose = false; + } + + if ( mustClose && !isSame && !isClose ) { + int balance = 0; + int unmatchedRight = 0; + const String& lineText = line( sel.start().line() ).getText(); + Int64 len = lineText.size(); + Int64 limitLeft = eemax( 0, sel.start().column() - 512 ); + Int64 limitRight = eemin( len, sel.start().column() + 512 ); + for ( Int64 k = limitLeft; k < limitRight; ++k ) { + if ( lineText[k] == text[0] ) { + balance++; + } else if ( lineText[k] == closeChar ) { + if ( balance > 0 ) { + balance--; + } else if ( k >= sel.start().column() ) { + unmatchedRight++; + } + } + } + if ( unmatchedRight > 0 ) mustClose = false; } if ( mustClose ) { - /* // I'm not entirely convinced about this - TextPosition openStart = positionOffset( sel.start(), 1 ); - if ( openStart != sel.start() ) { - int maxIt = 100; - while ( maxIt-- > 0 && openStart < endOfDoc() && - isSpace( getChar( openStart ) ) ) { - openStart = nextChar( openStart ); - } - if ( openStart < endOfDoc() && maxIt > 0 && - getChar( openStart ) == closeChar ) { - inserted.push_back( false ); - continue; - } - } - */ - setSelection( i, positionOffset( insert( i, sel.start(), text + String( closeChar ) ), -1 ) ); inserted.push_back( true ); diff --git a/src/tests/unit_tests/textdocument.cpp b/src/tests/unit_tests/textdocument.cpp index 6e57a4281..9b77df811 100644 --- a/src/tests/unit_tests/textdocument.cpp +++ b/src/tests/unit_tests/textdocument.cpp @@ -113,9 +113,9 @@ UTEST( TextDocument, newLineMultiCursorAutoIndent ) { doc.insert( 0, { 2, 3 }, ")" ); // Cursors between all pairs - doc.resetSelection( TextRanges( std::vector{ - TextRange( { 0, 1 }, { 0, 1 } ), TextRange( { 1, 2 }, { 1, 2 } ), - TextRange( { 2, 3 }, { 2, 3 } ) } ) ); + doc.resetSelection( TextRanges( std::vector{ TextRange( { 0, 1 }, { 0, 1 } ), + TextRange( { 1, 2 }, { 1, 2 } ), + TextRange( { 2, 3 }, { 2, 3 } ) } ) ); doc.newLine(); @@ -145,4 +145,48 @@ UTEST( TextDocument, newLineNormal ) { EXPECT_STDSTREQ( TextPosition( 1, 2 ).toString(), doc.getSelection().start().toString() ); } +UTEST( TextDocument, autoCloseBrackets ) { + TextDocument doc; + doc.setAutoCloseBrackets( true ); + // Test word boundary + doc.insert( 0, { 0, 0 }, "word" ); + doc.setSelection( { 0, 0 } ); // Before 'word' + doc.textInput( "(" ); // Next char 'w' is a word char, shouldn't auto close + EXPECT_STRINGEQ( "(word\n", doc.line( 0 ).getText() ); + + doc.reset(); + doc.setAutoCloseBrackets( true ); + doc.insert( 0, { 0, 0 }, " word" ); + doc.setSelection( { 0, 0 } ); // Before ' word' + doc.textInput( "(" ); // Next char ' ' is not a word char, should auto close + EXPECT_STRINGEQ( "() word\n", doc.line( 0 ).getText() ); + + doc.reset(); + doc.setAutoCloseBrackets( true ); + doc.insert( 0, { 0, 0 }, "() )" ); + doc.setSelection( { 0, 1 } ); // Inside first parens + doc.textInput( "(" ); // Unmatched right paren ahead, shouldn't auto close + EXPECT_STRINGEQ( "(() )\n", doc.line( 0 ).getText() ); + + doc.reset(); + doc.setAutoCloseBrackets( true ); + doc.insert( 0, { 0, 0 }, "()" ); + doc.setSelection( { 0, 1 } ); // Inside first parens + doc.textInput( "(" ); // Balanced right paren ahead, should auto close + EXPECT_STRINGEQ( "(())\n", doc.line( 0 ).getText() ); + + doc.reset(); + doc.setAutoCloseBrackets( true ); + doc.insert( 0, { 0, 0 }, "(\"\")" ); + doc.setSelection( { 0, 2 } ); // Inside quotes + doc.textInput( "\"" ); // Overwrites existing quote (stepping over) + EXPECT_STRINGEQ( "(\"\")\n", doc.line( 0 ).getText() ); + + doc.reset(); + doc.setAutoCloseBrackets( true ); + doc.insert( 0, { 0, 0 }, "()" ); + doc.setSelection( { 0, 1 } ); // Inside parens + doc.textInput( "\"" ); // Balanced quotes (0), should auto close + EXPECT_STRINGEQ( "(\"\")\n", doc.line( 0 ).getText() ); +}