mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-30 18:16:31 +03:00
Merge branch 'develop' into debugger
This commit is contained in:
@@ -34,13 +34,13 @@
|
||||
},
|
||||
{
|
||||
"language": "rust",
|
||||
"file_patterns": ["%.rs"],
|
||||
"file_patterns": ["%.rs$"],
|
||||
"command": "rustfmt --emit stdout --color never $FILENAME",
|
||||
"url": "https://rust-lang.github.io/rustfmt/"
|
||||
},
|
||||
{
|
||||
"language": "go",
|
||||
"file_patterns": ["%.go"],
|
||||
"file_patterns": ["%.go$"],
|
||||
"command": "gopls format $FILENAME",
|
||||
"url": "https://pkg.go.dev/golang.org/x/tools/gopls"
|
||||
},
|
||||
@@ -53,21 +53,21 @@
|
||||
},
|
||||
{
|
||||
"language": [ "xml" ],
|
||||
"file_patterns": ["%.xml"],
|
||||
"file_patterns": ["%.xml$"],
|
||||
"command": "xml",
|
||||
"type": "native",
|
||||
"url": "#native"
|
||||
},
|
||||
{
|
||||
"language": "css",
|
||||
"file_patterns": ["%.css"],
|
||||
"file_patterns": ["%.css$"],
|
||||
"command": "css",
|
||||
"type": "native",
|
||||
"url": "#native"
|
||||
},
|
||||
{
|
||||
"language": "zig",
|
||||
"file_patterns": ["%.zig"],
|
||||
"file_patterns": ["%.zig$"],
|
||||
"command": "zig fmt $FILENAME",
|
||||
"type": "inplace",
|
||||
"url": "https://ziglang.org"
|
||||
|
||||
@@ -132,21 +132,21 @@
|
||||
"name": "lua-language-server",
|
||||
"url": "https://github.com/sumneko/lua-language-server",
|
||||
"command": "lua-language-server",
|
||||
"file_patterns": ["%.lua"]
|
||||
"file_patterns": ["%.lua$"]
|
||||
},
|
||||
{
|
||||
"language": "kotlin",
|
||||
"name": "kotlin-language-server",
|
||||
"url": "https://github.com/fwcd/kotlin-language-server",
|
||||
"command": "kotlin-language-server",
|
||||
"file_patterns": ["%.kt"]
|
||||
"file_patterns": ["%.kt$"]
|
||||
},
|
||||
{
|
||||
"language": "nim",
|
||||
"name": "nimlsp",
|
||||
"url": "https://github.com/PMunch/nimlsp",
|
||||
"command": "nimlsp",
|
||||
"file_patterns": ["%.nim"]
|
||||
"file_patterns": ["%.nim$"]
|
||||
},
|
||||
{
|
||||
"language": "ruby",
|
||||
@@ -154,28 +154,28 @@
|
||||
"url": "https://solargraph.org",
|
||||
"command": "solargraph stdio",
|
||||
"rootIndicationFileNames": ["Gemfile", "Gemfile.lock", "gems.rb", "gems.lock", "Rakefile"],
|
||||
"file_patterns": ["%.rb"]
|
||||
"file_patterns": ["%.rb$"]
|
||||
},
|
||||
{
|
||||
"language": "yaml",
|
||||
"name": "yaml-language-server",
|
||||
"url": "https://github.com/redhat-developer/yaml-language-server",
|
||||
"command": "yaml-language-server --stdio",
|
||||
"file_patterns": ["%.yaml", "%.yml"]
|
||||
"file_patterns": ["%.yaml$", "%.yml$"]
|
||||
},
|
||||
{
|
||||
"language": "dart",
|
||||
"name": "dart language-server",
|
||||
"url": "https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server/tool/lsp_spec",
|
||||
"command": "dart language-server --client-id ecode",
|
||||
"file_patterns": ["%.dart"]
|
||||
"file_patterns": ["%.dart$"]
|
||||
},
|
||||
{
|
||||
"language": "shellscript",
|
||||
"name": "bash-language-server",
|
||||
"url": "https://github.com/bash-lsp/bash-language-server",
|
||||
"command": "bash-language-server start",
|
||||
"file_patterns": ["%.sh", "%.bash"]
|
||||
"file_patterns": ["%.sh$", "%.bash$"]
|
||||
},
|
||||
{
|
||||
"language": "html",
|
||||
@@ -317,7 +317,7 @@
|
||||
"name": "glsl_analyzer",
|
||||
"url": "https://github.com/nolanderc/glsl_analyzer",
|
||||
"command": "glsl_analyzer",
|
||||
"file_patterns": ["%.glsl$", "%.frag$", "%.vert$", "%.fs$", "%.vs$", "%.tesc", "%.tese"]
|
||||
"file_patterns": ["%.glsl$", "%.frag$", "%.vert$", "%.fs$", "%.vs$", "%.tesc$", "%.tese$"]
|
||||
},
|
||||
{
|
||||
"language": "vala",
|
||||
@@ -366,7 +366,7 @@
|
||||
"name": "LanguageServer.jl",
|
||||
"url": "https://github.com/julia-vscode/LanguageServer.jl",
|
||||
"command": "julia --project=\"$HOME/.julia/packages/LanguageServer/Fwm1f/src/LanguageServer.jl\" -e \"using LanguageServer; runserver()\"",
|
||||
"file_patterns": ["%.jl"]
|
||||
"file_patterns": ["%.jl$"]
|
||||
},
|
||||
{
|
||||
"language": "fortran",
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
#define EE_UI_DOC_TEXTRANGE_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <eepp/core/debug.hpp>
|
||||
#include <eepp/ui/doc/textposition.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Doc {
|
||||
|
||||
class EE_API TextRange {
|
||||
public:
|
||||
TextRange() {}
|
||||
TextRange( const TextPosition& start, const TextPosition& end ) :
|
||||
mStart( start ), mEnd( end ) {}
|
||||
TextRange();
|
||||
|
||||
bool isValid() const { return mStart.isValid() && mEnd.isValid(); }
|
||||
TextRange( const TextPosition& start, const TextPosition& end );
|
||||
|
||||
void clear() {
|
||||
mStart = {};
|
||||
mEnd = {};
|
||||
}
|
||||
bool isValid() const;
|
||||
|
||||
void clear();
|
||||
|
||||
TextPosition& start() { return mStart; }
|
||||
|
||||
@@ -28,14 +24,9 @@ class EE_API TextRange {
|
||||
|
||||
const TextPosition& end() const { return mEnd; }
|
||||
|
||||
TextRange normalized() const { return TextRange( normalizedStart(), normalizedEnd() ); }
|
||||
TextRange normalized() const;
|
||||
|
||||
TextRange& normalize() {
|
||||
auto normalize( normalized() );
|
||||
mStart = normalize.start();
|
||||
mEnd = normalize.end();
|
||||
return *this;
|
||||
}
|
||||
TextRange& normalize();
|
||||
|
||||
void reverse() { std::swap( mEnd, mStart ); }
|
||||
|
||||
@@ -45,10 +36,7 @@ class EE_API TextRange {
|
||||
|
||||
void setEnd( const TextPosition& position ) { mEnd = position; }
|
||||
|
||||
void set( const TextPosition& start, const TextPosition& end ) {
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
}
|
||||
void set( const TextPosition& start, const TextPosition& end );
|
||||
|
||||
bool operator==( const TextRange& other ) const {
|
||||
return mStart == other.mStart && mEnd == other.mEnd;
|
||||
@@ -90,21 +78,9 @@ class EE_API TextRange {
|
||||
return TextRange( mStart - other.mStart, mEnd - other.mEnd );
|
||||
}
|
||||
|
||||
bool contains( const TextPosition& position ) const {
|
||||
if ( !( position.line() > mStart.line() ||
|
||||
( position.line() == mStart.line() && position.column() >= mStart.column() ) ) )
|
||||
return false;
|
||||
if ( !( position.line() < mEnd.line() ||
|
||||
( position.line() == mEnd.line() && position.column() <= mEnd.column() ) ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool contains( const TextPosition& position ) const;
|
||||
|
||||
bool intersectsLineRange( const TextRange& range ) const {
|
||||
eeASSERT( range.isNormalized() );
|
||||
return mStart.line() <= static_cast<Int64>( range.end().line() ) &&
|
||||
static_cast<Int64>( range.start().line() ) <= mEnd.line();
|
||||
}
|
||||
bool intersectsLineRange( const TextRange& range ) const;
|
||||
|
||||
template <typename T> bool intersectsLineRange( T fromLine, T toLine ) const {
|
||||
return mStart.line() <= static_cast<Int64>( toLine ) &&
|
||||
@@ -116,44 +92,25 @@ class EE_API TextRange {
|
||||
static_cast<Int64>( range.first ) <= mEnd.line();
|
||||
}
|
||||
|
||||
bool containsLine( const Int64& line ) const {
|
||||
return line >= mStart.line() && line <= mEnd.line();
|
||||
}
|
||||
bool containsLine( const Int64& line ) const;
|
||||
|
||||
bool contains( const TextRange& range ) const {
|
||||
return range.start() >= start() && range.end() <= end();
|
||||
}
|
||||
bool contains( const TextRange& range ) const;
|
||||
|
||||
bool intersects( const TextRange& range ) const;
|
||||
|
||||
TextRange merge( const TextRange& range ) const;
|
||||
|
||||
bool hasSelection() const { return isValid() && mStart != mEnd; }
|
||||
|
||||
bool inSameLine() const { return isValid() && mStart.line() == mEnd.line(); }
|
||||
|
||||
Int64 height() const {
|
||||
if ( mEnd.line() > mStart.line() )
|
||||
return mEnd.line() - mStart.line() + 1;
|
||||
return mStart.line() - mStart.line() + 1;
|
||||
}
|
||||
Int64 height() const;
|
||||
|
||||
Int64 length() const {
|
||||
if ( !inSameLine() )
|
||||
return 0;
|
||||
if ( mEnd.column() > mStart.column() )
|
||||
return mEnd.column() - mStart.column();
|
||||
return mStart.column() - mEnd.column();
|
||||
}
|
||||
Int64 length() const;
|
||||
|
||||
std::string toString() const {
|
||||
return String::format( "%s - %s", mStart.toString().c_str(), mEnd.toString().c_str() );
|
||||
}
|
||||
std::string toString() const;
|
||||
|
||||
static TextRange fromString( const std::string& range ) {
|
||||
auto split = String::split( range, "-" );
|
||||
if ( split.size() == 2 ) {
|
||||
return { TextPosition::fromString( String::trim( split[0] ) ),
|
||||
TextPosition::fromString( String::trim( split[1] ) ) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
static TextRange fromString( const std::string& range );
|
||||
|
||||
bool isNormalized() const { return mStart <= mEnd; }
|
||||
|
||||
@@ -168,96 +125,31 @@ class EE_API TextRange {
|
||||
|
||||
class EE_API TextRanges : public std::vector<TextRange> {
|
||||
public:
|
||||
TextRanges() {}
|
||||
TextRanges();
|
||||
|
||||
TextRanges( const std::vector<TextRange>& ranges ) : std::vector<TextRange>( ranges ) {}
|
||||
TextRanges( const std::vector<TextRange>& ranges );
|
||||
|
||||
TextRanges( const TextRange& ranges ) : std::vector<TextRange>( { ranges } ) {}
|
||||
TextRanges( const TextRange& ranges );
|
||||
|
||||
bool isSorted() const { return mIsSorted; }
|
||||
bool isSorted() const;
|
||||
|
||||
bool isValid() const {
|
||||
for ( const auto& selection : *this ) {
|
||||
if ( !selection.isValid() )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool isValid() const;
|
||||
|
||||
bool exists( const TextRange& range ) const {
|
||||
if ( !mIsSorted )
|
||||
return std::find( begin(), end(), range ) != end();
|
||||
return std::binary_search( begin(), end(), range );
|
||||
}
|
||||
bool exists( const TextRange& range ) const;
|
||||
|
||||
size_t findIndex( const TextRange& range ) const {
|
||||
if ( !mIsSorted ) {
|
||||
auto it = std::find( begin(), end(), range );
|
||||
return it != end() ? std::distance( begin(), it ) : static_cast<size_t>( -1 );
|
||||
} else {
|
||||
auto it = std::lower_bound( begin(), end(), range );
|
||||
return ( it != end() && *it == range ) ? std::distance( begin(), it )
|
||||
: static_cast<size_t>( -1 );
|
||||
}
|
||||
}
|
||||
size_t findIndex( const TextRange& range ) const;
|
||||
|
||||
bool hasSelection() const {
|
||||
for ( const auto& r : *this )
|
||||
if ( r.hasSelection() )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool hasSelection() const;
|
||||
|
||||
void sort() {
|
||||
std::sort( begin(), end() );
|
||||
setSorted();
|
||||
}
|
||||
void sort();
|
||||
|
||||
void setSorted() { mIsSorted = true; }
|
||||
void setSorted();
|
||||
|
||||
bool merge() {
|
||||
if ( size() <= 1 )
|
||||
return false;
|
||||
bool merge();
|
||||
|
||||
if ( !mIsSorted )
|
||||
sort();
|
||||
std::string toString() const;
|
||||
|
||||
auto itUnique = std::unique(
|
||||
begin(), end(), []( const TextRange& a, const TextRange& b ) { return a == b; } );
|
||||
|
||||
bool merged = itUnique != end();
|
||||
erase( itUnique, end() );
|
||||
|
||||
auto it = begin();
|
||||
while ( it != end() ) {
|
||||
auto next = std::next( it );
|
||||
while ( next != end() && it != end() && next->contains( *it ) ) {
|
||||
erase( it );
|
||||
it = std::prev( next );
|
||||
}
|
||||
it = next;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
std::string str;
|
||||
for ( size_t i = 0; i < size(); ++i ) {
|
||||
str += ( *this )[i].toString();
|
||||
if ( i != size() - 1 )
|
||||
str += ";";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static TextRanges fromString( const std::string& str ) {
|
||||
auto rangesStr = String::split( str, ';' );
|
||||
TextRanges ranges;
|
||||
for ( const auto& rangeStr : rangesStr )
|
||||
ranges.emplace_back( TextRange::fromString( rangeStr ) );
|
||||
return ranges;
|
||||
}
|
||||
static TextRanges fromString( const std::string& str );
|
||||
|
||||
protected:
|
||||
bool mIsSorted{ false };
|
||||
|
||||
@@ -1127,6 +1127,7 @@
|
||||
../../src/eepp/ui/doc/syntaxtokenizer.cpp
|
||||
../../src/eepp/ui/doc/textdocument.cpp
|
||||
../../src/eepp/ui/doc/textformat.cpp
|
||||
../../src/eepp/ui/doc/textrange.cpp
|
||||
../../src/eepp/ui/doc/textundostack.cpp
|
||||
../../src/eepp/ui/doc/documentview.cpp
|
||||
../../src/eepp/ui/keyboardshortcut.cpp
|
||||
|
||||
@@ -8,7 +8,7 @@ void addC() {
|
||||
auto& sd = SyntaxDefinitionManager::instance()->add(
|
||||
|
||||
{ "C",
|
||||
{ "%.c$", "%.C", "%.h$", "%.icc" },
|
||||
{ "%.c$", "%.C$", "%.h$", "%.icc$" },
|
||||
{
|
||||
{ { "//.-\n" }, "comment" },
|
||||
{ { "/%*", "%*/" }, "comment" },
|
||||
|
||||
@@ -9,7 +9,7 @@ void addHTML() {
|
||||
->add(
|
||||
|
||||
{ "HTML",
|
||||
{ "%.[mp]?html?$", "%.handlebars" },
|
||||
{ "%.[mp]?html?$", "%.handlebars$" },
|
||||
{
|
||||
{ { "<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*['\"]%a+/"
|
||||
"[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||
|
||||
@@ -8,7 +8,7 @@ void addJSON() {
|
||||
auto& sd = SyntaxDefinitionManager::instance()->add(
|
||||
|
||||
{ "JSON",
|
||||
{ "%.json$", "%.cson$", "%.webmanifest" },
|
||||
{ "%.json$", "%.cson$", "%.webmanifest$" },
|
||||
{
|
||||
{ { "(%b\"\")(:)" }, { "normal", "keyword", "operator" } },
|
||||
{ { "\"", "\"", "\\" }, "string" },
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#include <eepp/ui/doc/languages/plaintext.hpp>
|
||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Doc { namespace Language {
|
||||
|
||||
void addPlaintext() {
|
||||
|
||||
SyntaxDefinitionManager::instance()->add(
|
||||
|
||||
{"Plain Text",
|
||||
{},
|
||||
{
|
||||
|
||||
},
|
||||
{
|
||||
|
||||
},
|
||||
"",
|
||||
{},
|
||||
"plaintext"
|
||||
});
|
||||
}
|
||||
|
||||
}}}} // namespace EE::UI::Doc::Language
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef EE_UI_DOC_Plaintext
|
||||
#define EE_UI_DOC_Plaintext
|
||||
|
||||
namespace EE { namespace UI { namespace Doc { namespace Language {
|
||||
|
||||
extern void addPlaintext();
|
||||
|
||||
}}}}
|
||||
|
||||
#endif
|
||||
@@ -37,7 +37,8 @@ SyntaxDefinitionManager::createSingleton( std::size_t reserveSpaceForLanguages )
|
||||
}
|
||||
|
||||
static void addPlainText() {
|
||||
SyntaxDefinitionManager::instance()->add( { "Plain Text", {}, {}, {}, "", {}, "plaintext" } );
|
||||
SyntaxDefinitionManager::instance()->add(
|
||||
{ "Plain Text", { "%.txt$" }, {}, {}, "", {}, "plaintext" } );
|
||||
}
|
||||
|
||||
// Syntax definitions can be directly converted from the lite (https://github.com/rxi/lite) and
|
||||
|
||||
@@ -59,7 +59,9 @@ TextDocument::~TextDocument() {
|
||||
Lock l( mLoadingMutex );
|
||||
}
|
||||
|
||||
{ Lock l( mClientsMutex ); }
|
||||
{
|
||||
Lock l( mClientsMutex );
|
||||
}
|
||||
|
||||
// Loading has been stopped
|
||||
while ( mLoadingAsync ) {
|
||||
@@ -1305,13 +1307,13 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
|
||||
mLines.emplace_back( String( "\n" ) );
|
||||
|
||||
if ( mSelection.size() > 1 ) {
|
||||
auto ranNorm( originalRange.normalized() );
|
||||
Int64 lineRem = ranNorm.end().line() - ranNorm.start().line();
|
||||
auto oriNm( originalRange.normalized() );
|
||||
Int64 lineRem = oriNm.end().line() - oriNm.start().line();
|
||||
size_t curIdx = 0;
|
||||
|
||||
Int64 colRem = ranNorm.start().line() == ranNorm.end().line()
|
||||
? ranNorm.end().column() - ranNorm.start().column()
|
||||
: ranNorm.end().column();
|
||||
Int64 colRem = oriNm.start().line() == oriNm.end().line()
|
||||
? oriNm.end().column() - oriNm.start().column()
|
||||
: oriNm.end().column();
|
||||
|
||||
for ( auto& sel : mSelection ) {
|
||||
if ( curIdx == cursorIdx ) {
|
||||
@@ -1319,9 +1321,10 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
|
||||
continue;
|
||||
}
|
||||
|
||||
auto selNorm( sel.normalized() );
|
||||
auto selNm( sel.normalized() );
|
||||
auto selOri( sel );
|
||||
|
||||
if ( selNorm.start().line() < ranNorm.end().line() ) {
|
||||
if ( selNm.start().line() < oriNm.end().line() ) {
|
||||
curIdx++;
|
||||
continue;
|
||||
}
|
||||
@@ -1329,13 +1332,22 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
|
||||
if ( lineRem != 0 ) {
|
||||
sel.start().setLine( sel.start().line() - lineRem );
|
||||
sel.end().setLine( sel.end().line() - lineRem );
|
||||
}
|
||||
|
||||
sel.start().setColumn( ranNorm.start().column() + sel.start().column() - colRem );
|
||||
sel.end().setColumn( ranNorm.start().column() + sel.end().column() - colRem );
|
||||
} else if ( selNorm.end().line() == ranNorm.end().line() &&
|
||||
ranNorm.end().column() <= selNorm.start().column() ) {
|
||||
sel.start().setColumn( sel.start().column() - colRem );
|
||||
sel.end().setColumn( sel.end().column() - colRem );
|
||||
if ( selOri.start().line() == oriNm.end().line() &&
|
||||
selOri.start().column() >= oriNm.end().column() ) {
|
||||
if ( sel.start().line() != selOri.start().line() )
|
||||
sel.start().setColumn( oriNm.start().column() + sel.start().column() - colRem );
|
||||
else
|
||||
sel.start().setColumn( sel.start().column() - colRem );
|
||||
}
|
||||
|
||||
if ( selOri.end().line() == oriNm.end().line() &&
|
||||
selOri.end().column() >= oriNm.end().column() ) {
|
||||
if ( selOri.end().line() != sel.end().line() )
|
||||
sel.end().setColumn( oriNm.start().column() + sel.end().column() - colRem );
|
||||
else
|
||||
sel.end().setColumn( sel.end().column() - colRem );
|
||||
}
|
||||
|
||||
sel = sanitizeRange( sel );
|
||||
@@ -1912,7 +1924,7 @@ void TextDocument::deleteToNextChar() {
|
||||
|
||||
void TextDocument::deleteToEndOfLine() {
|
||||
for ( size_t i = 0; i < mSelection.size(); ++i )
|
||||
deleteTo( i, endOfLine( getSelectionIndex( i ).start() ));
|
||||
deleteTo( i, endOfLine( getSelectionIndex( i ).start() ) );
|
||||
mergeSelection();
|
||||
}
|
||||
|
||||
|
||||
199
src/eepp/ui/doc/textrange.cpp
Normal file
199
src/eepp/ui/doc/textrange.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include <eepp/core/debug.hpp>
|
||||
#include <eepp/ui/doc/textrange.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Doc {
|
||||
|
||||
TextRange::TextRange() {}
|
||||
|
||||
TextRange::TextRange( const TextPosition& start, const TextPosition& end ) :
|
||||
mStart( start ), mEnd( end ) {}
|
||||
|
||||
bool TextRange::isValid() const {
|
||||
return mStart.isValid() && mEnd.isValid();
|
||||
}
|
||||
|
||||
void TextRange::clear() {
|
||||
mStart = {};
|
||||
mEnd = {};
|
||||
}
|
||||
|
||||
TextRange TextRange::normalized() const {
|
||||
return TextRange( normalizedStart(), normalizedEnd() );
|
||||
}
|
||||
|
||||
TextRange& TextRange::normalize() {
|
||||
auto normalize( normalized() );
|
||||
mStart = normalize.start();
|
||||
mEnd = normalize.end();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TextRange::set( const TextPosition& start, const TextPosition& end ) {
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
}
|
||||
|
||||
bool TextRange::contains( const TextPosition& position ) const {
|
||||
if ( !( position.line() > mStart.line() ||
|
||||
( position.line() == mStart.line() && position.column() >= mStart.column() ) ) )
|
||||
return false;
|
||||
if ( !( position.line() < mEnd.line() ||
|
||||
( position.line() == mEnd.line() && position.column() <= mEnd.column() ) ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextRange::intersectsLineRange( const TextRange& range ) const {
|
||||
eeASSERT( range.isNormalized() );
|
||||
return mStart.line() <= static_cast<Int64>( range.end().line() ) &&
|
||||
static_cast<Int64>( range.start().line() ) <= mEnd.line();
|
||||
}
|
||||
|
||||
bool TextRange::containsLine( const Int64& line ) const {
|
||||
return line >= mStart.line() && line <= mEnd.line();
|
||||
}
|
||||
|
||||
bool TextRange::contains( const TextRange& range ) const {
|
||||
return range.start() >= start() && range.end() <= end();
|
||||
}
|
||||
|
||||
bool TextRange::intersects( const TextRange& range ) const {
|
||||
return range.start() <= end() && range.end() >= start();
|
||||
}
|
||||
|
||||
TextRange TextRange::merge( const TextRange& range ) const {
|
||||
bool wasNormalized = mStart <= mEnd;
|
||||
TextRange normalizedThis = this->normalized();
|
||||
TextRange normalizedRange = range.normalized();
|
||||
TextPosition mergedStart = normalizedThis.start() < normalizedRange.start()
|
||||
? normalizedThis.start()
|
||||
: normalizedRange.start();
|
||||
TextPosition mergedEnd =
|
||||
normalizedThis.end() > normalizedRange.end() ? normalizedThis.end() : normalizedRange.end();
|
||||
return TextRange( wasNormalized ? mergedStart : mergedEnd,
|
||||
wasNormalized ? mergedEnd : mergedStart );
|
||||
}
|
||||
|
||||
Int64 TextRange::height() const {
|
||||
if ( mEnd.line() > mStart.line() )
|
||||
return mEnd.line() - mStart.line() + 1;
|
||||
return mStart.line() - mStart.line() + 1;
|
||||
}
|
||||
|
||||
Int64 TextRange::length() const {
|
||||
if ( !inSameLine() )
|
||||
return 0;
|
||||
if ( mEnd.column() > mStart.column() )
|
||||
return mEnd.column() - mStart.column();
|
||||
return mStart.column() - mEnd.column();
|
||||
}
|
||||
|
||||
std::string TextRange::toString() const {
|
||||
return String::format( "%s - %s", mStart.toString().c_str(), mEnd.toString().c_str() );
|
||||
}
|
||||
|
||||
TextRange TextRange::fromString( const std::string& range ) {
|
||||
auto split = String::split( range, "-" );
|
||||
if ( split.size() == 2 ) {
|
||||
return { TextPosition::fromString( String::trim( split[0] ) ),
|
||||
TextPosition::fromString( String::trim( split[1] ) ) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
TextRanges::TextRanges() {}
|
||||
|
||||
TextRanges::TextRanges( const std::vector<TextRange>& ranges ) : std::vector<TextRange>( ranges ) {}
|
||||
|
||||
TextRanges::TextRanges( const TextRange& ranges ) : std::vector<TextRange>( { ranges } ) {}
|
||||
|
||||
bool TextRanges::isSorted() const {
|
||||
return mIsSorted;
|
||||
}
|
||||
|
||||
bool TextRanges::isValid() const {
|
||||
for ( const auto& selection : *this ) {
|
||||
if ( !selection.isValid() )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextRanges::exists( const TextRange& range ) const {
|
||||
if ( !mIsSorted )
|
||||
return std::find( begin(), end(), range ) != end();
|
||||
return std::binary_search( begin(), end(), range );
|
||||
}
|
||||
|
||||
size_t TextRanges::findIndex( const TextRange& range ) const {
|
||||
if ( !mIsSorted ) {
|
||||
auto it = std::find( begin(), end(), range );
|
||||
return it != end() ? std::distance( begin(), it ) : static_cast<size_t>( -1 );
|
||||
} else {
|
||||
auto it = std::lower_bound( begin(), end(), range );
|
||||
return ( it != end() && *it == range ) ? std::distance( begin(), it )
|
||||
: static_cast<size_t>( -1 );
|
||||
}
|
||||
}
|
||||
|
||||
bool TextRanges::hasSelection() const {
|
||||
for ( const auto& r : *this )
|
||||
if ( r.hasSelection() )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextRanges::sort() {
|
||||
std::sort( begin(), end() );
|
||||
setSorted();
|
||||
}
|
||||
|
||||
void TextRanges::setSorted() {
|
||||
mIsSorted = true;
|
||||
}
|
||||
|
||||
bool TextRanges::merge() {
|
||||
if ( size() <= 1 )
|
||||
return false;
|
||||
|
||||
if ( !mIsSorted )
|
||||
sort();
|
||||
|
||||
auto itUnique = std::unique( begin(), end(),
|
||||
[]( const TextRange& a, const TextRange& b ) { return a == b; } );
|
||||
|
||||
bool merged = itUnique != end();
|
||||
erase( itUnique, end() );
|
||||
|
||||
for ( auto it = begin(); it != end(); ++it ) {
|
||||
auto next = std::next( it );
|
||||
while ( next != end() &&
|
||||
( it->intersects( *next ) || it->contains( *next ) || next->contains( *it ) ) ) {
|
||||
*it = it->merge( *next );
|
||||
next = erase( next );
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
std::string TextRanges::toString() const {
|
||||
std::string str;
|
||||
for ( size_t i = 0; i < size(); ++i ) {
|
||||
str += ( *this )[i].toString();
|
||||
if ( i != size() - 1 )
|
||||
str += ";";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
TextRanges TextRanges::fromString( const std::string& str ) {
|
||||
auto rangesStr = String::split( str, ';' );
|
||||
TextRanges ranges;
|
||||
for ( const auto& rangeStr : rangesStr )
|
||||
ranges.emplace_back( TextRange::fromString( rangeStr ) );
|
||||
return ranges;
|
||||
}
|
||||
|
||||
}}} // namespace EE::UI::Doc
|
||||
@@ -3130,6 +3130,7 @@ void UICodeEditor::selectToPreviousLine() {
|
||||
TextPosition position = mDoc->getSelectionIndex( i ).start();
|
||||
mDoc->selectTo( i, moveToLineOffset( position, -1 ) );
|
||||
}
|
||||
mDoc->mergeSelection();
|
||||
}
|
||||
|
||||
void UICodeEditor::selectToNextLine() {
|
||||
@@ -3141,6 +3142,7 @@ void UICodeEditor::selectToNextLine() {
|
||||
mDoc->selectTo( i, moveToLineOffset( position, 1 ) );
|
||||
}
|
||||
}
|
||||
mDoc->mergeSelection();
|
||||
}
|
||||
|
||||
void UICodeEditor::moveScrollUp() {
|
||||
|
||||
@@ -8,7 +8,7 @@ void addGLSL() {
|
||||
auto& sd = SyntaxDefinitionManager::instance()->add(
|
||||
|
||||
{ "GLSL",
|
||||
{ "%.glsl$", "%.frag$", "%.vert$", "%.fs$", "%.vs$", "%.tesc", "%.tese" },
|
||||
{ "%.glsl$", "%.frag$", "%.vert$", "%.fs$", "%.vs$", "%.tesc$", "%.tese$" },
|
||||
{
|
||||
{ { "//.-\n" }, "comment" },
|
||||
{ { "/%*", "%*/" }, "comment" },
|
||||
|
||||
@@ -8,7 +8,7 @@ void addRuby() {
|
||||
auto& sd = SyntaxDefinitionManager::instance()->add(
|
||||
|
||||
{ "Ruby",
|
||||
{ "%.rb", "%.gemspec", "%.ruby" },
|
||||
{ "%.rb$", "%.gemspec$", "%.ruby$" },
|
||||
{
|
||||
{ { "\"", "\"", "\\" }, "string" },
|
||||
{ { "'", "'", "\\" }, "string" },
|
||||
|
||||
76
src/tests/unit_tests/textdocument.cpp
Normal file
76
src/tests/unit_tests/textdocument.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "utest.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
|
||||
using namespace EE::UI::Doc;
|
||||
using namespace EE::System;
|
||||
|
||||
UTEST( TextDocument, multicursor ) {
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
TextDocument doc;
|
||||
doc.loadFromFile( "assets/textformat/english.utf8.lf.nobom.txt" );
|
||||
EXPECT_EQ( doc.linesCount() > 0, true );
|
||||
|
||||
// Same line delete
|
||||
for ( int op = 0; op < 2; op++ ) {
|
||||
doc.setSelection( { { 0, 4 }, { 0, 5 } } );
|
||||
|
||||
EXPECT_STRINGEQ( "a", doc.getSelectedText() );
|
||||
|
||||
// Select all "a" from first line
|
||||
doc.selectWord();
|
||||
doc.selectWord();
|
||||
doc.selectWord();
|
||||
|
||||
switch ( op ) {
|
||||
case 0:
|
||||
doc.deleteToPreviousChar();
|
||||
break;
|
||||
case 1:
|
||||
doc.deleteToNextChar();
|
||||
break;
|
||||
}
|
||||
|
||||
EXPECT_STRINGEQ( "It ws bright cold dy in April, nd the clocks were striking thirteen.\n",
|
||||
doc.line( 0 ).getText() );
|
||||
|
||||
doc.resetSelection( TextRange{ { 0, 0 }, { 0, 0 } } );
|
||||
doc.undo();
|
||||
}
|
||||
|
||||
// Multi-line delete
|
||||
for ( int op = 0; op < 4; op++ ) {
|
||||
TextRanges ranges(
|
||||
{ TextRange( { 3, 65 }, { 4, 11 } ), TextRange( { 17, 66 }, { 18, 67 } ) } );
|
||||
|
||||
if ( op >= 2 )
|
||||
for ( auto& range : ranges )
|
||||
range.reverse();
|
||||
|
||||
doc.resetSelection( ranges );
|
||||
|
||||
switch ( op % 2 ) {
|
||||
case 0:
|
||||
doc.deleteToPreviousChar();
|
||||
break;
|
||||
case 1:
|
||||
doc.deleteToNextChar();
|
||||
break;
|
||||
}
|
||||
|
||||
EXPECT_STRINGEQ( "though not quickly enough to prevent a swirl of gritty dust from him.\n",
|
||||
doc.line( 3 ).getText() );
|
||||
EXPECT_STRINGEQ( "one of those pictures which are so contrived that the eyes follow ran.\n",
|
||||
doc.line( 16 ).getText() );
|
||||
EXPECT_STDSTREQ( TextRange( { 3, 65 }, { 3, 65 } ).toString(),
|
||||
doc.getSelectionIndex( 0 ).toString() );
|
||||
EXPECT_STDSTREQ( TextRange( { 16, 66 }, { 16, 66 } ).toString(),
|
||||
doc.getSelectionIndex( 1 ).toString() );
|
||||
|
||||
doc.undo();
|
||||
}
|
||||
|
||||
doc.resetUndoRedo();
|
||||
doc.resetSelection( TextRange{ { 0, 0 }, { 0, 0 } } );
|
||||
}
|
||||
52
src/tests/unit_tests/utest.hpp
Normal file
52
src/tests/unit_tests/utest.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "utest.h"
|
||||
|
||||
#define UTEST_STDSTREQ(x, y, msg, is_assert) \
|
||||
UTEST_SURPRESS_WARNING_BEGIN do { \
|
||||
const std::string xEval = (x); \
|
||||
const std::string yEval = (y); \
|
||||
if (xEval != yEval) { \
|
||||
UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
|
||||
UTEST_PRINTF(" Expected : \"%s\"\n", xEval.c_str()); \
|
||||
UTEST_PRINTF(" Actual : \"%s\"\n", yEval.c_str()); \
|
||||
if (strlen(msg) > 0) { \
|
||||
UTEST_PRINTF(" Message : %s\n", msg); \
|
||||
} \
|
||||
*utest_result = UTEST_TEST_FAILURE; \
|
||||
if (is_assert) { \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
while (0) \
|
||||
UTEST_SURPRESS_WARNING_END
|
||||
|
||||
#define EXPECT_STDSTREQ(x, y) UTEST_STDSTREQ(x, y, "", 0)
|
||||
#define EXPECT_STDSTREQ_MSG(x, y, msg) UTEST_STDSTREQ(x, y, msg, 0)
|
||||
#define ASSERT_STDSTREQ(x, y) UTEST_STDSTREQ(x, y, "", 1)
|
||||
#define ASSERT_STDSTREQ_MSG(x, y, msg) UTEST_STDSTREQ(x, y, msg, 1)
|
||||
|
||||
#define UTEST_STRINGEQ(x, y, msg, is_assert) \
|
||||
UTEST_SURPRESS_WARNING_BEGIN do { \
|
||||
const String xEval = (x); \
|
||||
const String yEval = (y); \
|
||||
if (xEval != yEval) { \
|
||||
UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
|
||||
UTEST_PRINTF(" Expected : \"%s\"\n", xEval.toUtf8().c_str()); \
|
||||
UTEST_PRINTF(" Actual : \"%s\"\n", yEval.toUtf8().c_str()); \
|
||||
if (strlen(msg) > 0) { \
|
||||
UTEST_PRINTF(" Message : %s\n", msg); \
|
||||
} \
|
||||
*utest_result = UTEST_TEST_FAILURE; \
|
||||
if (is_assert) { \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
while (0) \
|
||||
UTEST_SURPRESS_WARNING_END
|
||||
|
||||
#define EXPECT_STRINGEQ(x, y) UTEST_STRINGEQ(x, y, "", 0)
|
||||
#define EXPECT_STRINGEQ_MSG(x, y, msg) UTEST_STRINGEQ(x, y, msg, 0)
|
||||
#define ASSERT_STRINGEQ(x, y) UTEST_STRINGEQ(x, y, "", 1)
|
||||
#define ASSERT_STRINGEQ_MSG(x, y, msg) UTEST_STRINGEQ(x, y, msg, 1)
|
||||
Reference in New Issue
Block a user