mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-30 18:16:31 +03:00
ecode: Added XML Tools plugin, currently provides highlight of matching xml tags and auto edit of xml tag name. Some minor fixes are still pending.
This commit is contained in:
@@ -36,6 +36,26 @@ class BoolScopedOp {
|
||||
bool& boolRef;
|
||||
};
|
||||
|
||||
class BoolScopedOpOptional {
|
||||
public:
|
||||
explicit BoolScopedOpOptional( bool cond, bool& boolRef ) : cond( cond ), boolRef( boolRef ) {}
|
||||
|
||||
explicit BoolScopedOpOptional( bool cond, bool& boolRef, bool initialVal ) :
|
||||
cond( cond ), boolRef( boolRef ) {
|
||||
if ( cond )
|
||||
boolRef = initialVal;
|
||||
}
|
||||
|
||||
~BoolScopedOpOptional() {
|
||||
if ( cond )
|
||||
boolRef = !boolRef;
|
||||
}
|
||||
|
||||
private:
|
||||
bool cond;
|
||||
bool& boolRef;
|
||||
};
|
||||
|
||||
}} // namespace EE::System
|
||||
|
||||
#endif // EE_SYSTEM_SCOPEDOP_HPP
|
||||
|
||||
@@ -556,13 +556,13 @@ class EE_API TextDocument {
|
||||
|
||||
SyntaxHighlighter* getHighlighter() const;
|
||||
|
||||
TextRange getWordRangeInPosition( const TextPosition& pos );
|
||||
TextRange getWordRangeInPosition( const TextPosition& pos, bool basedOnHighlighter = true );
|
||||
|
||||
TextRange getWordRangeInPosition();
|
||||
TextRange getWordRangeInPosition( bool basedOnHighlighter = true );
|
||||
|
||||
String getWordInPosition( const TextPosition& pos );
|
||||
String getWordInPosition( const TextPosition& pos, bool basedOnHighlighter = true );
|
||||
|
||||
String getWordInPosition();
|
||||
String getWordInPosition( bool basedOnHighlighter = true );
|
||||
|
||||
bool mightBeBinary() const;
|
||||
|
||||
@@ -578,6 +578,10 @@ class EE_API TextDocument {
|
||||
|
||||
void stopActiveFindAll();
|
||||
|
||||
bool isDoingTextInput() const;
|
||||
|
||||
bool isInsertingText() const;
|
||||
|
||||
protected:
|
||||
friend class UndoStack;
|
||||
|
||||
@@ -609,6 +613,7 @@ class EE_API TextDocument {
|
||||
bool mHAsCpp{ false };
|
||||
bool mLastCursorChangeWasInteresting{ false };
|
||||
bool mDoingTextInput{ false };
|
||||
bool mInsertingText{ false };
|
||||
std::vector<std::pair<String::StringBaseType, String::StringBaseType>> mAutoCloseBracketsPairs;
|
||||
Uint32 mIndentWidth{ 4 };
|
||||
IndentType mIndentType{ IndentType::IndentTabs };
|
||||
|
||||
@@ -71,6 +71,22 @@ class EE_API TextRange {
|
||||
return mStart >= other.mStart && mEnd >= other.mEnd;
|
||||
}
|
||||
|
||||
TextRange operator+( const TextRange& other ) const {
|
||||
return TextRange( mStart + other.mStart, mEnd + other.mEnd );
|
||||
}
|
||||
|
||||
TextRange operator+=( const TextRange& other ) const {
|
||||
return TextRange( mStart + other.mStart, mEnd + other.mEnd );
|
||||
}
|
||||
|
||||
TextRange operator-( const TextRange& other ) const {
|
||||
return TextRange( mStart - other.mStart, mEnd - other.mEnd );
|
||||
}
|
||||
|
||||
TextRange operator-=( const TextRange& other ) const {
|
||||
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() ) ) )
|
||||
|
||||
@@ -1238,6 +1238,8 @@
|
||||
../../src/tools/ecode/plugins/lsp/lspprotocol.hpp
|
||||
../../src/tools/ecode/plugins/pluginmanager.cpp
|
||||
../../src/tools/ecode/plugins/pluginmanager.hpp
|
||||
../../src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
|
||||
../../src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
|
||||
../../src/tools/ecode/projectbuild.cpp
|
||||
../../src/tools/ecode/projectbuild.hpp
|
||||
../../src/tools/ecode/projectdirectorytree.cpp
|
||||
|
||||
@@ -1266,7 +1266,7 @@ TextRange TextDocument::getDocRange() const {
|
||||
|
||||
void TextDocument::deleteTo( const size_t& cursorIdx, int offset ) {
|
||||
eeASSERT( cursorIdx < mSelection.size() );
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
TextPosition cursorPos = mSelection[cursorIdx].normalized().start();
|
||||
if ( mSelection[cursorIdx].hasSelection() ) {
|
||||
remove( cursorIdx, getSelectionIndex( cursorIdx ) );
|
||||
@@ -1281,7 +1281,7 @@ void TextDocument::deleteTo( const size_t& cursorIdx, int offset ) {
|
||||
}
|
||||
|
||||
void TextDocument::deleteSelection( const size_t& cursorIdx ) {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
TextPosition cursorPos = getSelectionIndex( cursorIdx ).normalized().start();
|
||||
remove( cursorIdx, getSelectionIndex( cursorIdx ) );
|
||||
setSelection( cursorIdx, cursorPos );
|
||||
@@ -1377,6 +1377,7 @@ std::vector<bool> TextDocument::autoCloseBrackets( const String& text ) {
|
||||
|
||||
void TextDocument::textInput( const String& text, bool mightBeInteresting ) {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOp op2( mInsertingText, true );
|
||||
|
||||
if ( mAutoCloseBrackets && 1 == text.size() ) {
|
||||
auto inserted = autoCloseBrackets( text );
|
||||
@@ -1574,7 +1575,7 @@ void TextDocument::deleteToNextWord() {
|
||||
}
|
||||
|
||||
void TextDocument::deleteCurrentLine() {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
for ( size_t i = 0; i < mSelection.size(); ++i ) {
|
||||
if ( mSelection[i].hasSelection() ) {
|
||||
deleteSelection( i );
|
||||
@@ -1615,8 +1616,8 @@ void TextDocument::selectToNextWord() {
|
||||
mergeSelection();
|
||||
}
|
||||
|
||||
TextRange TextDocument::getWordRangeInPosition( const TextPosition& pos ) {
|
||||
if ( mHighlighter ) {
|
||||
TextRange TextDocument::getWordRangeInPosition( const TextPosition& pos, bool basedOnHighlighter ) {
|
||||
if ( mHighlighter && basedOnHighlighter ) {
|
||||
auto type( mHighlighter->getTokenPositionAt( pos ) );
|
||||
return { { pos.line(), type.pos }, { pos.line(), type.pos + (Int64)type.len } };
|
||||
}
|
||||
@@ -1624,16 +1625,16 @@ TextRange TextDocument::getWordRangeInPosition( const TextPosition& pos ) {
|
||||
return { nextWordBoundary( pos, false ), previousWordBoundary( pos, false ) };
|
||||
}
|
||||
|
||||
TextRange TextDocument::getWordRangeInPosition() {
|
||||
return getWordRangeInPosition( getSelection().start() );
|
||||
TextRange TextDocument::getWordRangeInPosition( bool basedOnHighlighter ) {
|
||||
return getWordRangeInPosition( getSelection().start(), basedOnHighlighter );
|
||||
}
|
||||
|
||||
String TextDocument::getWordInPosition( const TextPosition& pos ) {
|
||||
return getText( getWordRangeInPosition( pos ) );
|
||||
String TextDocument::getWordInPosition( const TextPosition& pos, bool basedOnHighlighter ) {
|
||||
return getText( getWordRangeInPosition( pos, basedOnHighlighter ) );
|
||||
}
|
||||
|
||||
String TextDocument::getWordInPosition() {
|
||||
return getWordInPosition( getSelection().start() );
|
||||
String TextDocument::getWordInPosition( bool basedOnHighlighter ) {
|
||||
return getWordInPosition( getSelection().start(), basedOnHighlighter );
|
||||
}
|
||||
|
||||
bool TextDocument::mightBeBinary() const {
|
||||
@@ -1771,7 +1772,7 @@ void TextDocument::newLineAbove() {
|
||||
}
|
||||
|
||||
void TextDocument::insertAtStartOfSelectedLines( const String& text, bool skipEmpty ) {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
TextPosition prevStart = getSelection().start();
|
||||
TextRange range = getSelection( true );
|
||||
bool swap = prevStart != range.start();
|
||||
@@ -1787,7 +1788,7 @@ void TextDocument::insertAtStartOfSelectedLines( const String& text, bool skipEm
|
||||
|
||||
void TextDocument::removeFromStartOfSelectedLines( const String& text, bool skipEmpty,
|
||||
bool removeExtraSpaces ) {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
TextPosition prevStart = getSelection().start();
|
||||
TextRange range = getSelection( true );
|
||||
bool swap = prevStart != range.start();
|
||||
@@ -1904,7 +1905,7 @@ void TextDocument::setIndentWidth( const Uint32& tabWidth ) {
|
||||
}
|
||||
|
||||
void TextDocument::deleteTo( const size_t& cursorIdx, TextPosition position ) {
|
||||
BoolScopedOp op( mDoingTextInput, true );
|
||||
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
|
||||
TextPosition cursorPos = getSelectionIndex( cursorIdx ).normalized().start();
|
||||
if ( getSelectionIndex( cursorIdx ).hasSelection() ) {
|
||||
remove( cursorIdx, getSelectionIndex( cursorIdx ) );
|
||||
@@ -2384,6 +2385,14 @@ void TextDocument::stopActiveFindAll() {
|
||||
*stopFlag.second.get() = true;
|
||||
}
|
||||
|
||||
bool TextDocument::isDoingTextInput() const {
|
||||
return mDoingTextInput;
|
||||
}
|
||||
|
||||
bool TextDocument::isInsertingText() const {
|
||||
return mInsertingText;
|
||||
}
|
||||
|
||||
TextRanges TextDocument::findAll( const String& text, bool caseSensitive, bool wholeWord,
|
||||
const FindReplaceType& type, TextRange restrictRange,
|
||||
size_t maxResults ) {
|
||||
@@ -2543,120 +2552,97 @@ TextPosition TextDocument::getMatchingBracket( TextPosition sp,
|
||||
return {};
|
||||
}
|
||||
|
||||
TextRange TextDocument::getMatchingBracket( TextPosition sp, const String& openBracket,
|
||||
TextRange TextDocument::getMatchingBracket( TextPosition start, const String& openBracket,
|
||||
const String& closeBracket, MatchDirection dir ) {
|
||||
if ( !sp.isValid() )
|
||||
if ( !start.isValid() )
|
||||
return {};
|
||||
SyntaxHighlighter* highlighter = getHighlighter();
|
||||
if ( dir == MatchDirection::Forward ) {
|
||||
{
|
||||
TextPosition end( positionOffset( sp, openBracket.size() ) );
|
||||
TextPosition end( positionOffset( start, openBracket.size() ) );
|
||||
// Skip the open string if the start position is from there. Always start with depth 1
|
||||
if ( end.isValid() ) {
|
||||
String text = getText( { sp, end } );
|
||||
String text = getText( { start, end } );
|
||||
if ( text == openBracket )
|
||||
sp = end;
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there's a close bracket
|
||||
auto foundClose = find( closeBracket, sp );
|
||||
auto foundClose = find( closeBracket, start );
|
||||
if ( !foundClose.isValid() )
|
||||
return {}; // Not found, exit
|
||||
|
||||
TextRange foundOpen = { sp, sp };
|
||||
TextRange foundOpen = { start, start };
|
||||
int depth = 1;
|
||||
|
||||
do {
|
||||
foundOpen = find( openBracket, foundOpen.end(), true, false,
|
||||
TextDocument::FindReplaceType::Normal,
|
||||
{ foundOpen.end(), foundClose.start() } );
|
||||
if ( foundOpen.isValid() )
|
||||
changeDepth( highlighter, depth, foundOpen.start(), 1 );
|
||||
} while ( foundOpen.isValid() );
|
||||
|
||||
// Didn't fint more open brackets, the depth is the same, we found the close correct bracket
|
||||
if ( depth == 1 )
|
||||
return foundClose;
|
||||
|
||||
// Start balanced search from the first close bracket found
|
||||
sp = foundClose.end();
|
||||
do {
|
||||
auto findOpen = find( openBracket, sp );
|
||||
if ( findOpen.isValid() ) {
|
||||
changeDepth( highlighter, depth, findOpen.start(), 1 );
|
||||
sp = findOpen.end();
|
||||
foundClose = find( closeBracket, sp );
|
||||
if ( foundClose.isValid() ) {
|
||||
changeDepth( highlighter, depth, foundClose.start(), -1 );
|
||||
// Find all the open brackets between the first open bracket and the first close bracket
|
||||
do {
|
||||
foundOpen =
|
||||
find( openBracket, start, true, false, TextDocument::FindReplaceType::Normal,
|
||||
{ start, foundClose.start() } );
|
||||
if ( foundOpen.isValid() ) {
|
||||
start = foundOpen.end();
|
||||
changeDepth( highlighter, depth, start, 1 );
|
||||
} else {
|
||||
break; // Unexpected, fail
|
||||
}
|
||||
} else {
|
||||
foundClose = find( closeBracket, sp );
|
||||
if ( foundClose.isValid() ) {
|
||||
changeDepth( highlighter, depth, foundClose.start(), -1 );
|
||||
sp = foundClose.end();
|
||||
} else {
|
||||
break; // Unexpected, fail
|
||||
start = foundClose.end();
|
||||
changeDepth( highlighter, depth, start, -1 );
|
||||
}
|
||||
} while ( foundOpen.isValid() );
|
||||
|
||||
if ( depth > 0 ) {
|
||||
// Find the next close bracket from the last close bracket
|
||||
foundClose = find( closeBracket, start );
|
||||
if ( !foundClose.isValid() )
|
||||
break;
|
||||
}
|
||||
} while ( depth > 0 );
|
||||
|
||||
return foundClose;
|
||||
} else {
|
||||
{
|
||||
TextPosition end( positionOffset( sp, -closeBracket.size() ) );
|
||||
// Skip the cloes string if the start position is from there. Always start with depth 1
|
||||
TextPosition end( positionOffset( start, -closeBracket.size() ) );
|
||||
// Skip the close string if the start position is from there. Always start with depth 1
|
||||
if ( end.isValid() ) {
|
||||
String text = getText( { end, sp } );
|
||||
String text = getText( { end, start } );
|
||||
if ( text == closeBracket )
|
||||
sp = end;
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there's an open bracket
|
||||
auto foundOpen = findLast( openBracket, sp );
|
||||
auto foundOpen = findLast( openBracket, start );
|
||||
if ( !foundOpen.isValid() )
|
||||
return {}; // Not found, exit
|
||||
|
||||
TextRange foundClose = { sp, sp };
|
||||
TextRange foundClose = { start, start };
|
||||
int depth = 1;
|
||||
|
||||
do {
|
||||
foundClose = findLast( closeBracket, foundClose.end(), true, false,
|
||||
TextDocument::FindReplaceType::Normal,
|
||||
{ foundClose.end(), foundOpen.start() } );
|
||||
if ( foundClose.isValid() )
|
||||
changeDepth( highlighter, depth, foundClose.start(), 1 );
|
||||
} while ( foundClose.isValid() );
|
||||
|
||||
// Didn't fint more open brackets, the depth is the same, we found the close correct bracket
|
||||
if ( depth == 1 )
|
||||
return foundOpen;
|
||||
|
||||
// Start balanced search from the first open bracket found
|
||||
sp = foundOpen.end();
|
||||
do {
|
||||
auto findClose = findLast( closeBracket, sp );
|
||||
if ( findClose.isValid() ) {
|
||||
changeDepth( highlighter, depth, findClose.start(), 1 );
|
||||
sp = findClose.end();
|
||||
foundOpen = findLast( openBracket, sp );
|
||||
if ( foundOpen.isValid() ) {
|
||||
changeDepth( highlighter, depth, foundOpen.start(), -1 );
|
||||
// Find all the close brackets between the first close bracket and the first open
|
||||
// bracket
|
||||
do {
|
||||
foundClose =
|
||||
findLast( closeBracket, start, true, false,
|
||||
TextDocument::FindReplaceType::Normal, { start, foundOpen.start() } );
|
||||
if ( foundClose.isValid() ) {
|
||||
start = foundClose.end();
|
||||
changeDepth( highlighter, depth, start, 1 );
|
||||
} else {
|
||||
break; // Unexpected, fail
|
||||
}
|
||||
} else {
|
||||
foundOpen = findLast( openBracket, sp );
|
||||
if ( foundOpen.isValid() ) {
|
||||
changeDepth( highlighter, depth, foundOpen.start(), -1 );
|
||||
sp = foundOpen.end();
|
||||
} else {
|
||||
break; // Unexpected, fail
|
||||
start = foundOpen.end();
|
||||
changeDepth( highlighter, depth, start, -1 );
|
||||
}
|
||||
} while ( foundClose.isValid() );
|
||||
|
||||
if ( depth > 0 ) {
|
||||
// Find the next open bracket from the last open bracket
|
||||
foundOpen = findLast( openBracket, start );
|
||||
if ( !foundOpen.isValid() )
|
||||
break;
|
||||
}
|
||||
} while ( depth > 0 );
|
||||
|
||||
return foundOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "plugins/formatter/formatterplugin.hpp"
|
||||
#include "plugins/linter/linterplugin.hpp"
|
||||
#include "plugins/lsp/lspclientplugin.hpp"
|
||||
#include "plugins/xmltools/xmltoolsplugin.hpp"
|
||||
#include "settingsmenu.hpp"
|
||||
#include "uibuildsettings.hpp"
|
||||
#include "uiwelcomescreen.hpp"
|
||||
@@ -429,6 +430,7 @@ void App::initPluginManager() {
|
||||
mPluginManager->registerPlugin( FormatterPlugin::Definition() );
|
||||
mPluginManager->registerPlugin( AutoCompletePlugin::Definition() );
|
||||
mPluginManager->registerPlugin( LSPClientPlugin::Definition() );
|
||||
mPluginManager->registerPlugin( XMLToolsPlugin::Definition() );
|
||||
}
|
||||
|
||||
void App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool sync,
|
||||
@@ -3508,8 +3510,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
std::vector<std::string> adedLangs;
|
||||
if ( SyntaxDefinitionManager::instance()->loadFromStream( sfile, &adedLangs ) ) {
|
||||
for ( const auto& lang : adedLangs ) {
|
||||
const auto& def =
|
||||
SyntaxDefinitionManager::instance()->getByLanguageName( lang );
|
||||
const auto& def = SyntaxDefinitionManager::instance()->getByLanguageName( lang );
|
||||
auto code = SyntaxDefinitionManager::toCPP( def );
|
||||
if ( convertLangOutput && !convertLangOutput.Get().empty() &&
|
||||
FileSystem::isDirectory( convertLangOutput.Get() ) ) {
|
||||
|
||||
@@ -43,9 +43,6 @@ FormatterPlugin::FormatterPlugin( PluginManager* pluginManager, bool sync ) :
|
||||
load( pluginManager );
|
||||
#endif
|
||||
}
|
||||
mManager->subscribeMessages( this, [this]( const PluginMessage& msg ) -> PluginRequestHandle {
|
||||
return processMessage( msg );
|
||||
} );
|
||||
}
|
||||
|
||||
FormatterPlugin::~FormatterPlugin() {
|
||||
@@ -187,7 +184,9 @@ void FormatterPlugin::loadFormatterConfig( const std::string& path, bool updateC
|
||||
j["keybindings"]["format-doc"] = mKeyBindings["format-doc"];
|
||||
|
||||
if ( updateConfigFile ) {
|
||||
FileSystem::fileWrite( path, j.dump( 2 ) );
|
||||
data = j.dump( 2 );
|
||||
FileSystem::fileWrite( path, data );
|
||||
mConfigHash = String::hash( data );
|
||||
}
|
||||
|
||||
if ( !j.contains( "formatters" ) )
|
||||
@@ -265,11 +264,11 @@ void FormatterPlugin::load( PluginManager* pluginManager ) {
|
||||
}
|
||||
if ( paths.empty() )
|
||||
return;
|
||||
for ( const auto& path : paths ) {
|
||||
for ( const auto& fpath : paths ) {
|
||||
try {
|
||||
loadFormatterConfig( path, mConfigPath == path );
|
||||
loadFormatterConfig( fpath, mConfigPath == fpath );
|
||||
} catch ( const json::exception& e ) {
|
||||
Log::error( "Parsing formatter \"%s\" failed:\n%s", path.c_str(), e.what() );
|
||||
Log::error( "Parsing formatter \"%s\" failed:\n%s", fpath.c_str(), e.what() );
|
||||
}
|
||||
}
|
||||
mReady = !mFormatters.empty();
|
||||
|
||||
@@ -496,6 +496,17 @@ PluginManager* Plugin::getManager() const {
|
||||
return mManager;
|
||||
}
|
||||
|
||||
PluginBase::~PluginBase() {
|
||||
mShuttingDown = true;
|
||||
unsubscribeFileSystemListener();
|
||||
for ( auto editor : mEditors ) {
|
||||
onBeforeUnregister( editor.first );
|
||||
for ( auto listener : editor.second )
|
||||
editor.first->removeEventListener( listener );
|
||||
editor.first->unregisterPlugin( this );
|
||||
}
|
||||
}
|
||||
|
||||
void PluginBase::onRegister( UICodeEditor* editor ) {
|
||||
Lock l( mMutex );
|
||||
|
||||
@@ -514,8 +525,9 @@ void PluginBase::onRegister( UICodeEditor* editor ) {
|
||||
Lock l( mMutex );
|
||||
const DocEvent* docEvent = static_cast<const DocEvent*>( event );
|
||||
TextDocument* doc = docEvent->getDoc();
|
||||
mDocs.erase( doc );
|
||||
onDocumentClosed( doc );
|
||||
onUnregisterDocument( doc );
|
||||
mDocs.erase( doc );
|
||||
}
|
||||
} ) );
|
||||
|
||||
@@ -532,7 +544,10 @@ void PluginBase::onRegister( UICodeEditor* editor ) {
|
||||
onRegisterListeners( editor, listeners );
|
||||
|
||||
mEditors.insert( { editor, listeners } );
|
||||
mDocs.insert( editor->getDocumentRef().get() );
|
||||
if ( mDocs.count( editor->getDocumentRef().get() ) == 0 ) {
|
||||
mDocs.insert( editor->getDocumentRef().get() );
|
||||
onRegisterDocument( editor->getDocumentRef().get() );
|
||||
}
|
||||
mEditorDocs[editor] = editor->getDocumentRef().get();
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ enum class PluginMessageType {
|
||||
// available
|
||||
GetErrorOrWarning, // Request a component to provide the information of an error or warning in a
|
||||
// particular document location
|
||||
GetDiagnostics, // Request the diagnostic information from a cursor position
|
||||
GetDiagnostics, // Request the diagnostic information from a cursor position
|
||||
Undefined
|
||||
};
|
||||
|
||||
@@ -424,9 +424,15 @@ class Plugin : public UICodeEditorPlugin {
|
||||
|
||||
class PluginBase : public Plugin {
|
||||
public:
|
||||
virtual void onRegister( UICodeEditor* );
|
||||
explicit PluginBase( PluginManager* manager ) : Plugin( manager ) {}
|
||||
|
||||
virtual void onUnregister( UICodeEditor* );
|
||||
virtual ~PluginBase();
|
||||
|
||||
virtual void onRegister( UICodeEditor* ) override;
|
||||
|
||||
virtual void onUnregister( UICodeEditor* ) override;
|
||||
|
||||
virtual String::HashType getConfigFileHash() override { return mConfigHash; }
|
||||
|
||||
protected:
|
||||
//! Keep track of the registered editors + all the listeners registered to each editor
|
||||
@@ -437,6 +443,10 @@ class PluginBase : public Plugin {
|
||||
Mutex mMutex;
|
||||
//! Keep track of the document pointer of each editor
|
||||
std::unordered_map<UICodeEditor*, TextDocument*> mEditorDocs;
|
||||
//! Keep track of the key bindings managed by the plugin
|
||||
std::map<std::string, std::string> mKeyBindings; /* cmd, shortcut */
|
||||
//! If the configuration is stored in a file, keep track of the config hash
|
||||
String::HashType mConfigHash{ 0 };
|
||||
|
||||
virtual void onDocumentLoaded( TextDocument* ){};
|
||||
|
||||
@@ -449,6 +459,8 @@ class PluginBase : public Plugin {
|
||||
//! Usually used to remove keybindings in an editor
|
||||
virtual void onBeforeUnregister( UICodeEditor* ){};
|
||||
|
||||
virtual void onRegisterDocument( TextDocument* ){};
|
||||
|
||||
virtual void onUnregisterEditor( UICodeEditor* ){};
|
||||
|
||||
//! Usually used to unregister commands in a document
|
||||
|
||||
340
src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
Normal file
340
src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
#include "xmltoolsplugin.hpp"
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/scopedop.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
|
||||
#define XMLTOOLS_THREADED 1
|
||||
#else
|
||||
#define XMLTOOLS_THREADED 0
|
||||
#endif
|
||||
|
||||
namespace ecode {
|
||||
|
||||
UICodeEditorPlugin* XMLToolsPlugin::New( PluginManager* pluginManager ) {
|
||||
return eeNew( XMLToolsPlugin, ( pluginManager, false ) );
|
||||
}
|
||||
|
||||
UICodeEditorPlugin* XMLToolsPlugin::NewSync( PluginManager* pluginManager ) {
|
||||
return eeNew( XMLToolsPlugin, ( pluginManager, true ) );
|
||||
}
|
||||
|
||||
XMLToolsPlugin::XMLToolsPlugin( PluginManager* pluginManager, bool sync ) :
|
||||
PluginBase( pluginManager ) {
|
||||
if ( sync ) {
|
||||
load( pluginManager );
|
||||
} else {
|
||||
#if FORMATTER_THREADED
|
||||
mThreadPool->run( [&, pluginManager] { load( pluginManager ); } );
|
||||
#else
|
||||
load( pluginManager );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
XMLToolsPlugin::~XMLToolsPlugin() {
|
||||
mShuttingDown = true;
|
||||
{
|
||||
Lock l( mClientsMutex );
|
||||
for ( const auto& client : mClients )
|
||||
client.first->unregisterClient( client.second.get() );
|
||||
}
|
||||
}
|
||||
|
||||
bool XMLToolsPlugin::getHighlightMatch() const {
|
||||
return mHighlightMatch;
|
||||
}
|
||||
|
||||
bool XMLToolsPlugin::getAutoEditMatch() const {
|
||||
return mAutoEditMatch;
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::load( PluginManager* pluginManager ) {
|
||||
BoolScopedOp loading( mLoading, true );
|
||||
std::string path = pluginManager->getPluginsPath() + "xmltools.json";
|
||||
if ( FileSystem::fileExists( path ) ||
|
||||
FileSystem::fileWrite( path, "{\n \"config\":{},\n \"keybindings\":{}\n}\n" ) ) {
|
||||
mConfigPath = path;
|
||||
}
|
||||
std::string data;
|
||||
if ( !FileSystem::fileGet( path, data ) )
|
||||
return;
|
||||
mConfigHash = String::hash( data );
|
||||
|
||||
json j;
|
||||
try {
|
||||
j = json::parse( data, nullptr, true, true );
|
||||
} catch ( const json::exception& e ) {
|
||||
Log::error( "XMLToolsPlugin::load - Error parsing config from path %s, error: %s, config "
|
||||
"file content:\n%s",
|
||||
path.c_str(), e.what(), data.c_str() );
|
||||
// Recreate it
|
||||
j = json::parse( "{\n \"config\":{},\n \"keybindings\":{},\n}\n", nullptr, true, true );
|
||||
}
|
||||
|
||||
bool updateConfigFile = false;
|
||||
|
||||
if ( j.contains( "config" ) ) {
|
||||
auto& config = j["config"];
|
||||
if ( config.contains( "highlight_match" ) )
|
||||
mHighlightMatch = config.value( "highlight_match", true );
|
||||
else {
|
||||
config["highlight_match"] = mHighlightMatch;
|
||||
updateConfigFile = true;
|
||||
}
|
||||
if ( config.contains( "auto_edit_match" ) )
|
||||
mAutoEditMatch = config.value( "auto_edit_match", true );
|
||||
else {
|
||||
config["auto_edit_match"] = mAutoEditMatch;
|
||||
updateConfigFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( updateConfigFile ) {
|
||||
data = j.dump( 2 );
|
||||
FileSystem::fileWrite( path, data );
|
||||
mConfigHash = String::hash( data );
|
||||
}
|
||||
|
||||
fireReadyCbs();
|
||||
subscribeFileSystemListener();
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::onRegisterDocument( TextDocument* doc ) {
|
||||
Lock l( mClientsMutex );
|
||||
mClients[doc] = std::make_unique<XMLToolsClient>( this, doc );
|
||||
doc->registerClient( mClients[doc].get() );
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::onUnregisterDocument( TextDocument* doc ) {
|
||||
Lock l( mClientsMutex );
|
||||
doc->unregisterClient( mClients[doc].get() );
|
||||
mClients.erase( doc );
|
||||
}
|
||||
|
||||
bool XMLToolsPlugin::isOverMatch( TextDocument* doc, const Int64& index ) const {
|
||||
if ( mMatches.empty() )
|
||||
return false;
|
||||
auto clientIt = mMatches.find( doc );
|
||||
if ( clientIt == mMatches.end() )
|
||||
return false;
|
||||
const ClientMatch& match = clientIt->second;
|
||||
if ( match.matchBracket.start().line() != index &&
|
||||
match.currentBracket.start().line() != index )
|
||||
return false;
|
||||
if ( !match.matchBracket.inSameLine() && !match.currentBracket.inSameLine() )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isClosedTag( TextDocument* doc, TextPosition start ) {
|
||||
SyntaxHighlighter* highlighter = doc->getHighlighter();
|
||||
TextPosition endOfDoc = doc->endOfDoc();
|
||||
String::StringBaseType prevChar = '\0';
|
||||
do {
|
||||
String::StringBaseType ch = doc->getChar( start );
|
||||
if ( ch == '>' ) {
|
||||
auto tokenType = highlighter->getTokenTypeAt( start );
|
||||
if ( tokenType != "comment" && tokenType != "string" )
|
||||
return prevChar == '/';
|
||||
}
|
||||
start = doc->positionOffset( start, 1 );
|
||||
prevChar = ch;
|
||||
} while ( start.isValid() && start != endOfDoc );
|
||||
return false;
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::XMLToolsClient::onDocumentTextChanged(
|
||||
const DocumentContentChange& docChange ) {
|
||||
if ( mAutoInserting || !mParent->getAutoEditMatch() ||
|
||||
!mParent->isOverMatch( mDoc, docChange.range.start().line() ) ||
|
||||
mDoc->getSelections().size() > 1 )
|
||||
return;
|
||||
ClientMatch& match = mParent->mMatches[mDoc];
|
||||
if ( !match.currentBracket.contains( docChange.range ) )
|
||||
return;
|
||||
mAutoInserting = true;
|
||||
auto sel = mDoc->getSelections();
|
||||
auto diff = docChange.range.start() - match.currentBracket.start() +
|
||||
( match.currentIsClose ? TextPosition( 0, 0 ) : TextPosition( 0, 1 ) );
|
||||
auto translatedPos = match.matchBracket.normalize().start() + diff;
|
||||
if ( match.currentIsClose ) {
|
||||
translatedPos = mDoc->positionOffset( translatedPos, -1 );
|
||||
}
|
||||
mDoc->setSelection( 0, translatedPos );
|
||||
auto translation =
|
||||
docChange.range.normalized().end().column() - docChange.range.normalized().start().column();
|
||||
if ( docChange.text.empty() ) {
|
||||
if ( match.currentBracket.start().line() == match.matchBracket.start().line() ) {
|
||||
if ( !match.currentIsClose ) {
|
||||
translatedPos = mDoc->positionOffset( translatedPos, -translation );
|
||||
}
|
||||
}
|
||||
mDoc->remove(
|
||||
0, { translatedPos, { translatedPos.line(), translatedPos.column() + translation } } );
|
||||
if ( mDoc->isInsertingText() ) {
|
||||
mWaitingText = true;
|
||||
} else {
|
||||
TextRange range = match.currentIsClose ? match.currentBracket : match.matchBracket;
|
||||
range.normalize();
|
||||
if ( match.isSameLine() && !match.currentIsClose ) {
|
||||
range.setStart( mDoc->positionOffset( range.start(), -translation + 1 ) );
|
||||
}
|
||||
auto closeText =
|
||||
mDoc->getText( { range.start(), mDoc->positionOffset( range.start(), 3 ) } );
|
||||
if ( closeText == "</>" ) {
|
||||
mJustDeletedWholeWord = true;
|
||||
if ( match.isSameLine() ) {
|
||||
match.currentBracket = { match.currentBracket.start(),
|
||||
mDoc->positionOffset( match.currentBracket.start(),
|
||||
match.currentIsClose ? 2 : 1 ) };
|
||||
match.matchBracket = {
|
||||
match.matchBracket.start(),
|
||||
mDoc->positionOffset( match.matchBracket.start(),
|
||||
match.currentIsClose ? 1 : 1 - translation ) };
|
||||
} else {
|
||||
match.currentBracket = { match.currentBracket.start(),
|
||||
mDoc->positionOffset( match.currentBracket.start(),
|
||||
match.currentIsClose ? 2 : 1 ) };
|
||||
match.matchBracket = { match.matchBracket.start(),
|
||||
mDoc->positionOffset( match.matchBracket.start(),
|
||||
match.currentIsClose ? 1 : 2 ) };
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( match.isSameLine() && !match.currentIsClose ) {
|
||||
translatedPos =
|
||||
mDoc->positionOffset( translatedPos, translation + docChange.text.size() );
|
||||
}
|
||||
mDoc->insert( 0, translatedPos, docChange.text );
|
||||
mWaitingText = false;
|
||||
if ( match.isSameLine() && match.currentIsClose ) {
|
||||
for ( auto& s : sel ) {
|
||||
s.start().setColumn( s.start().column() + docChange.text.size() * 2 + 1 );
|
||||
s.end().setColumn( s.end().column() + docChange.text.size() * 2 + 1 );
|
||||
}
|
||||
mForceSelections = true;
|
||||
mSelections = sel;
|
||||
}
|
||||
}
|
||||
if ( !mJustDeletedWholeWord )
|
||||
mAutoInserting = false;
|
||||
mDoc->setSelection( sel );
|
||||
mAutoInserting = false;
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::XMLToolsClient::updateMatch( const TextRange& sel ) {
|
||||
const auto& line = mDoc->line( mDoc->getSelection().start().line() ).getText();
|
||||
if ( mDoc->getSelection().start().column() >= (Int64)line.size() )
|
||||
return clearMatch();
|
||||
auto def = mDoc->getHighlighter()->getSyntaxDefinitionFromTextPosition( sel.start() );
|
||||
if ( !def.getAutoCloseXMLTags() ) // getAutoCloseXMLTags means that it supports XML element tags
|
||||
return clearMatch();
|
||||
TextRange range = mDoc->getWordRangeInPosition( sel.start(), false );
|
||||
if ( !range.isValid() )
|
||||
return clearMatch();
|
||||
range.normalize();
|
||||
if ( range.start().column() == 0 || line.size() <= 1 )
|
||||
return clearMatch();
|
||||
if ( line[range.start().column() - 1] != '<' && line[range.start().column() - 1] != '/' &&
|
||||
( range.start().column() - 2 < 0 || range.start().column() - 2 >= (Int64)line.size() ||
|
||||
line[range.start().column() - 2] != '<' ) )
|
||||
return clearMatch();
|
||||
bool isCloseBracket = line[range.start().column() - 1] == '/';
|
||||
if ( !isCloseBracket && isClosedTag( mDoc, range.end() ) )
|
||||
return clearMatch();
|
||||
range.start().setColumn( range.start().column() - ( isCloseBracket ? 2 : 1 ) );
|
||||
if ( mParent->mMatches.count( mDoc ) > 0 ) {
|
||||
const ClientMatch& curMatch( mParent->mMatches[mDoc] );
|
||||
if ( curMatch.currentBracket == range )
|
||||
return; // Moving inside match
|
||||
}
|
||||
String tag( mDoc->getText( range ) );
|
||||
|
||||
TextRange found;
|
||||
if ( isCloseBracket ) {
|
||||
String openBracket( tag );
|
||||
openBracket.erase( 1 );
|
||||
found = mDoc->getMatchingBracket( range.start(), openBracket, tag,
|
||||
TextDocument::MatchDirection::Backward );
|
||||
} else {
|
||||
String closeBracket( tag );
|
||||
closeBracket.insert( 1, '/' );
|
||||
found = mDoc->getMatchingBracket( range.start(), tag, closeBracket,
|
||||
TextDocument::MatchDirection::Forward );
|
||||
}
|
||||
|
||||
if ( found.isValid() ) {
|
||||
ClientMatch match{ range, found, isCloseBracket };
|
||||
mParent->mMatches[mDoc] = std::move( match );
|
||||
} else {
|
||||
clearMatch();
|
||||
}
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::XMLToolsClient::onDocumentSelectionChange( const TextRange& sel ) {
|
||||
if ( mForceSelections ) {
|
||||
mDoc->setSelection( mSelections );
|
||||
mForceSelections = false;
|
||||
}
|
||||
if ( mAutoInserting || mWaitingText )
|
||||
return;
|
||||
if ( mJustDeletedWholeWord ) {
|
||||
mJustDeletedWholeWord = false;
|
||||
return;
|
||||
}
|
||||
if ( !mParent->getHighlightMatch() && !mParent->getAutoEditMatch() )
|
||||
return clearMatch();
|
||||
if ( mDoc->getSelection().start().line() >= (Int64)mDoc->linesCount() )
|
||||
return clearMatch();
|
||||
updateMatch( sel );
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::XMLToolsClient::clearMatch() {
|
||||
if ( !mParent->mMatches.empty() )
|
||||
mParent->mMatches.erase( mDoc );
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::drawBeforeLineText( UICodeEditor* editor, const Int64& index,
|
||||
Vector2f position, const Float& /*fontSize*/,
|
||||
const Float& lineHeight ) {
|
||||
if ( !isOverMatch( &editor->getDocument(), index ) )
|
||||
return;
|
||||
Primitives p;
|
||||
Color color( editor->getColorScheme().getEditorSyntaxStyle( "matching_bracket" ).color );
|
||||
Color blendedColor( Color( color, 50 ) );
|
||||
p.setColor( blendedColor );
|
||||
|
||||
const ClientMatch& match = mMatches[&editor->getDocument()];
|
||||
for ( const auto& range : { match.matchBracket, match.currentBracket } ) {
|
||||
if ( range.start().line() != index || !range.inSameLine() )
|
||||
continue;
|
||||
Float offset1 = editor->getXOffsetCol( range.normalized().start() );
|
||||
Float offset2 = editor->getXOffsetCol( range.normalized().end() );
|
||||
p.drawRectangle(
|
||||
Rectf( { position.x + offset1, position.y }, { ( offset2 - offset1 ), lineHeight } ) );
|
||||
}
|
||||
}
|
||||
|
||||
void XMLToolsPlugin::minimapDrawAfterLineText( UICodeEditor* editor, const Int64& index,
|
||||
const Vector2f& pos, const Vector2f& size,
|
||||
const Float&, const Float& ) {
|
||||
if ( !isOverMatch( &editor->getDocument(), index ) )
|
||||
return;
|
||||
Primitives p;
|
||||
Color color( editor->getColorScheme().getEditorSyntaxStyle( "matching_bracket" ).color );
|
||||
Color blendedColor( Color( color, 50 ) );
|
||||
p.setColor( blendedColor );
|
||||
|
||||
const ClientMatch& match = mMatches[&editor->getDocument()];
|
||||
for ( const auto& range : { match.matchBracket, match.currentBracket } ) {
|
||||
if ( range.start().line() != index || !range.inSameLine() )
|
||||
continue;
|
||||
p.drawRectangle( Rectf( pos, size ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
112
src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
Normal file
112
src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#ifndef ECODE_XMLTOOLSPLUGIN_HPP
|
||||
#define ECODE_XMLTOOLSPLUGIN_HPP
|
||||
|
||||
#include "../pluginmanager.hpp"
|
||||
#include <eepp/config.hpp>
|
||||
#include <eepp/system/mutex.hpp>
|
||||
#include <eepp/system/threadpool.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <set>
|
||||
using namespace EE;
|
||||
using namespace EE::System;
|
||||
using namespace EE::UI;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
class XMLToolsPlugin : public PluginBase {
|
||||
public:
|
||||
static PluginDefinition Definition() {
|
||||
return { "xmltools",
|
||||
"XML Tools",
|
||||
"Simple tools to improve your XML editing experience.",
|
||||
XMLToolsPlugin::New,
|
||||
{ 0, 0, 1 },
|
||||
XMLToolsPlugin::NewSync };
|
||||
}
|
||||
|
||||
static UICodeEditorPlugin* New( PluginManager* pluginManager );
|
||||
|
||||
static UICodeEditorPlugin* NewSync( PluginManager* pluginManager );
|
||||
|
||||
virtual ~XMLToolsPlugin();
|
||||
|
||||
std::string getId() override { return Definition().id; }
|
||||
|
||||
std::string getTitle() override { return Definition().name; }
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool getHighlightMatch() const;
|
||||
|
||||
bool getAutoEditMatch() const;
|
||||
|
||||
void drawBeforeLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
||||
const Float& fontSize, const Float& lineHeight ) override;
|
||||
|
||||
void minimapDrawAfterLineText( UICodeEditor*, const Int64&, const Vector2f&, const Vector2f&,
|
||||
const Float&, const Float& ) override;
|
||||
|
||||
protected:
|
||||
bool mHighlightMatch{ true };
|
||||
bool mAutoEditMatch{ true };
|
||||
Mutex mClientsMutex;
|
||||
|
||||
class XMLToolsClient : public TextDocument::Client {
|
||||
public:
|
||||
explicit XMLToolsClient( XMLToolsPlugin* parent, TextDocument* doc ) :
|
||||
mDoc( doc ), mParent( parent ) {}
|
||||
|
||||
virtual void onDocumentTextChanged( const DocumentContentChange& );
|
||||
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ){};
|
||||
virtual void onDocumentCursorChange( const TextPosition& ){};
|
||||
virtual void onDocumentInterestingCursorChange( const TextPosition& ){};
|
||||
virtual void onDocumentSelectionChange( const TextRange& );
|
||||
virtual void onDocumentLineCountChange( const size_t&, const size_t& ){};
|
||||
virtual void onDocumentLineChanged( const Int64& ){};
|
||||
virtual void onDocumentSaved( TextDocument* ){};
|
||||
virtual void onDocumentClosed( TextDocument* ){};
|
||||
virtual void onDocumentDirtyOnFileSystem( TextDocument* ){};
|
||||
virtual void onDocumentMoved( TextDocument* ){};
|
||||
|
||||
protected:
|
||||
TextDocument* mDoc{ nullptr };
|
||||
XMLToolsPlugin* mParent{ nullptr };
|
||||
bool mAutoInserting{ false };
|
||||
bool mWaitingText{ false };
|
||||
bool mJustDeletedWholeWord{ false };
|
||||
bool mForceSelections{ false };
|
||||
TextRanges mSelections;
|
||||
|
||||
void updateMatch( const TextRange& range );
|
||||
|
||||
void clearMatch();
|
||||
};
|
||||
|
||||
using ClientsMap = std::unordered_map<TextDocument*, std::unique_ptr<XMLToolsClient>>;
|
||||
ClientsMap mClients;
|
||||
struct ClientMatch {
|
||||
TextRange currentBracket;
|
||||
TextRange matchBracket;
|
||||
bool currentIsClose;
|
||||
|
||||
bool isSameLine() const {
|
||||
return currentBracket.start().line() == matchBracket.start().line();
|
||||
}
|
||||
};
|
||||
using ClientsMatches = std::unordered_map<TextDocument*, ClientMatch>;
|
||||
ClientsMatches mMatches;
|
||||
|
||||
XMLToolsPlugin( PluginManager* pluginManager, bool sync );
|
||||
|
||||
void load( PluginManager* pluginManager );
|
||||
|
||||
virtual void onRegisterDocument( TextDocument* doc ) override;
|
||||
|
||||
virtual void onUnregisterDocument( TextDocument* doc ) override;
|
||||
|
||||
bool isOverMatch( TextDocument* doc, const Int64& index ) const;
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif // ECODE_XMLTOOLSPLUGIN_HPP
|
||||
Reference in New Issue
Block a user