mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
Optimize editor initialization and multi-file diff loading
- Share immutable built-in document commands, editor command tables, default keybindings, and syntax color scheme storage across editor instances. Preserve mutations through copy-on-write storage and explicit command-removal tracking. - Use ShortcutMap consistently with unordered lookup while retaining deterministic ordering where serialization or conflict resolution depends on it. Cache ambiguous syntax-extension checks and invalidate the cache when language definitions change. - Prepare Git commit-detail diffs and sub-line changes on worker threads with cancellation and stale-request protection. Release raw patch memory before UI construction and add regression coverage for prepared and cancelled diffs. - Add Diff Changed support and enable collapsible file headers for Diff HEAD, Diff Staged, and Diff Changes. - Update translations, compatibility call sites, tests, and the efsw dependency.
This commit is contained in:
@@ -37,7 +37,7 @@ namespace EE { namespace UI { namespace Doc {
|
||||
// "minimap_highlight" (Minimap text highlight color)
|
||||
// "minimap_visible_area" (Minimap visible area marker color)
|
||||
|
||||
SyntaxColorScheme SyntaxColorScheme::getDefaultDark() {
|
||||
static SyntaxColorScheme createDefaultDarkSyntaxColorScheme() {
|
||||
return {
|
||||
"eepp",
|
||||
{
|
||||
@@ -86,7 +86,12 @@ SyntaxColorScheme SyntaxColorScheme::getDefaultDark() {
|
||||
{ "minimap_visible_area"_sst, Color( "#FFFFFF0A" ) } } };
|
||||
}
|
||||
|
||||
SyntaxColorScheme SyntaxColorScheme::getDefaultLight() {
|
||||
SyntaxColorScheme SyntaxColorScheme::getDefaultDark() {
|
||||
static const SyntaxColorScheme colorScheme = createDefaultDarkSyntaxColorScheme();
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
static SyntaxColorScheme createDefaultLightSyntaxColorScheme() {
|
||||
return {
|
||||
"github",
|
||||
{
|
||||
@@ -135,6 +140,11 @@ SyntaxColorScheme SyntaxColorScheme::getDefaultLight() {
|
||||
{ "minimap_visible_area"_sst, Color( "#00000011" ) } } };
|
||||
}
|
||||
|
||||
SyntaxColorScheme SyntaxColorScheme::getDefaultLight() {
|
||||
static const SyntaxColorScheme colorScheme = createDefaultLightSyntaxColorScheme();
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
SyntaxColorScheme::Style parseStyle(
|
||||
const std::string& value, bool* colorWasSet = nullptr,
|
||||
const UnorderedMap<SyntaxStyleType, SyntaxColorScheme::Style>* syntaxColors = nullptr ) {
|
||||
@@ -212,11 +222,11 @@ std::vector<SyntaxColorScheme> SyntaxColorScheme::loadFromStream( IOStream& stre
|
||||
std::string value( ini.getValue( keyIdx, valueIdx ) );
|
||||
if ( !value.empty() ) {
|
||||
SyntaxColorScheme::Style style = parseStyle( value );
|
||||
if ( refColorScheme.mSyntaxColors.find( toSyntaxStyleType( valueName ) ) !=
|
||||
refColorScheme.mSyntaxColors.end() ) {
|
||||
if ( refColorScheme.mStorage->syntaxColors.find( toSyntaxStyleType( valueName ) ) !=
|
||||
refColorScheme.mStorage->syntaxColors.end() ) {
|
||||
colorScheme.setSyntaxStyle( toSyntaxStyleType( valueName ), style );
|
||||
} else if ( refColorScheme.mEditorColors.find( toSyntaxStyleType( valueName ) ) !=
|
||||
refColorScheme.mEditorColors.end() ) {
|
||||
} else if ( refColorScheme.mStorage->editorColors.find( toSyntaxStyleType(
|
||||
valueName ) ) != refColorScheme.mStorage->editorColors.end() ) {
|
||||
colorScheme.setEditorSyntaxStyle( toSyntaxStyleType( valueName ), style );
|
||||
}
|
||||
}
|
||||
@@ -257,20 +267,26 @@ std::vector<SyntaxColorScheme> SyntaxColorScheme::loadFromPack( Pack* pack,
|
||||
return {};
|
||||
}
|
||||
|
||||
SyntaxColorScheme::SyntaxColorScheme() {}
|
||||
SyntaxColorScheme::SyntaxColorScheme() : mStorage( std::make_shared<Storage>() ) {}
|
||||
|
||||
SyntaxColorScheme::SyntaxColorScheme( const std::string& name,
|
||||
const UnorderedMap<SyntaxStyleType, Style>& syntaxColors,
|
||||
const UnorderedMap<SyntaxStyleType, Style>& editorColors ) :
|
||||
mName( name ), mSyntaxColors( syntaxColors ), mEditorColors( editorColors ) {}
|
||||
mStorage( std::make_shared<Storage>( Storage{ name, syntaxColors, editorColors } ) ) {}
|
||||
|
||||
void SyntaxColorScheme::ensureUniqueStorage() {
|
||||
if ( !mStorage.unique() )
|
||||
mStorage = std::make_shared<Storage>( *mStorage );
|
||||
mStyleCache.clear();
|
||||
}
|
||||
|
||||
static const SyntaxColorScheme::Style StyleEmpty = { Color::Transparent };
|
||||
static const SyntaxColorScheme StyleDefault = SyntaxColorScheme::getDefaultDark();
|
||||
|
||||
const SyntaxColorScheme::Style&
|
||||
SyntaxColorScheme::getSyntaxStyle( const SyntaxStyleType& type ) const {
|
||||
auto it = mSyntaxColors.find( type );
|
||||
if ( it != mSyntaxColors.end() )
|
||||
auto it = mStorage->syntaxColors.find( type );
|
||||
if ( it != mStorage->syntaxColors.end() )
|
||||
return it->second;
|
||||
else if ( type == "keyword2"_sst )
|
||||
return getSyntaxStyle( "type"_sst );
|
||||
@@ -292,22 +308,24 @@ SyntaxColorScheme::getSyntaxStyle( const SyntaxStyleType& type ) const {
|
||||
}
|
||||
|
||||
bool SyntaxColorScheme::hasSyntaxStyle( const SyntaxStyleType& type ) const {
|
||||
return mSyntaxColors.find( type ) != mSyntaxColors.end();
|
||||
return mStorage->syntaxColors.find( type ) != mStorage->syntaxColors.end();
|
||||
}
|
||||
|
||||
void SyntaxColorScheme::setSyntaxStyles( const UnorderedMap<SyntaxStyleType, Style>& styles ) {
|
||||
mSyntaxColors.insert( styles.begin(), styles.end() );
|
||||
ensureUniqueStorage();
|
||||
mStorage->syntaxColors.insert( styles.begin(), styles.end() );
|
||||
}
|
||||
|
||||
void SyntaxColorScheme::setSyntaxStyle( const SyntaxStyleType& type,
|
||||
const SyntaxColorScheme::Style& style ) {
|
||||
mSyntaxColors[type] = style;
|
||||
ensureUniqueStorage();
|
||||
mStorage->syntaxColors[type] = style;
|
||||
}
|
||||
|
||||
const SyntaxColorScheme::Style&
|
||||
SyntaxColorScheme::getEditorSyntaxStyle( const SyntaxStyleType& type ) const {
|
||||
auto it = mEditorColors.find( type );
|
||||
if ( it != mEditorColors.end() )
|
||||
auto it = mStorage->editorColors.find( type );
|
||||
if ( it != mStorage->editorColors.end() )
|
||||
return it->second;
|
||||
if ( type == "widget_background"_sst )
|
||||
return getEditorSyntaxStyle( "gutter_background"_sst );
|
||||
@@ -348,20 +366,23 @@ const Color& SyntaxColorScheme::getEditorColor( const SyntaxStyleType& type ) co
|
||||
|
||||
void SyntaxColorScheme::setEditorSyntaxStyles(
|
||||
const EE::UnorderedMap<SyntaxStyleType, SyntaxColorScheme::Style>& styles ) {
|
||||
mEditorColors.insert( styles.begin(), styles.end() );
|
||||
ensureUniqueStorage();
|
||||
mStorage->editorColors.insert( styles.begin(), styles.end() );
|
||||
}
|
||||
|
||||
void SyntaxColorScheme::setEditorSyntaxStyle( const SyntaxStyleType& type,
|
||||
const SyntaxColorScheme::Style& style ) {
|
||||
mEditorColors[type] = style;
|
||||
ensureUniqueStorage();
|
||||
mStorage->editorColors[type] = style;
|
||||
}
|
||||
|
||||
const std::string& SyntaxColorScheme::getName() const {
|
||||
return mName;
|
||||
return mStorage->name;
|
||||
}
|
||||
|
||||
void SyntaxColorScheme::setName( const std::string& name ) {
|
||||
mName = name;
|
||||
ensureUniqueStorage();
|
||||
mStorage->name = name;
|
||||
}
|
||||
|
||||
template <typename SyntaxStyleType>
|
||||
@@ -369,18 +390,18 @@ const SyntaxColorScheme::Style&
|
||||
SyntaxColorScheme::getSyntaxStyleFromCache( const SyntaxStyleType& type ) const {
|
||||
bool colorWasSet = false;
|
||||
if constexpr ( std::is_same_v<SyntaxStyleType, std::string> )
|
||||
mStyleCache[type] = parseStyle( type, &colorWasSet, &mSyntaxColors );
|
||||
mStyleCache[type] = parseStyle( type, &colorWasSet, &mStorage->syntaxColors );
|
||||
else {
|
||||
auto cache = SyntaxPattern::SyntaxStyleTypeCache.find( type );
|
||||
if ( cache != SyntaxPattern::SyntaxStyleTypeCache.end() ) {
|
||||
mStyleCache[type] = parseStyle( cache->second, &colorWasSet, &mSyntaxColors );
|
||||
mStyleCache[type] = parseStyle( cache->second, &colorWasSet, &mStorage->syntaxColors );
|
||||
} else {
|
||||
return StyleEmpty;
|
||||
}
|
||||
}
|
||||
if ( !colorWasSet ) {
|
||||
auto normalStyle = mSyntaxColors.find( "normal"_sst );
|
||||
if ( normalStyle != mSyntaxColors.end() )
|
||||
auto normalStyle = mStorage->syntaxColors.find( "normal"_sst );
|
||||
if ( normalStyle != mStorage->syntaxColors.end() )
|
||||
mStyleCache[type].color = normalStyle->second.color;
|
||||
}
|
||||
return mStyleCache[type];
|
||||
|
||||
@@ -567,12 +567,14 @@ SyntaxDefinition& SyntaxDefinitionManager::add( SyntaxDefinition&& syntaxStyle )
|
||||
syntaxStyle.mLanguageIndex = mDefinitions.size();
|
||||
syntaxStyle.compile();
|
||||
mDefinitions.emplace_back( std::make_shared<SyntaxDefinition>( std::move( syntaxStyle ) ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
return *mDefinitions.back().get();
|
||||
}
|
||||
|
||||
void SyntaxDefinitionManager::addPreDefinition( SyntaxPreDefinition&& preDefinition ) {
|
||||
Lock l( mMutex );
|
||||
mPreDefinitions.emplace_back( std::move( preDefinition ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
}
|
||||
|
||||
const SyntaxDefinition& SyntaxDefinitionManager::getPlainDefinition() const {
|
||||
@@ -1104,6 +1106,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream,
|
||||
Lock l( mMutex );
|
||||
mDefinitions[pos.value()] =
|
||||
std::make_shared<SyntaxDefinition>( std::move( res ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
} else {
|
||||
if ( addedLangs )
|
||||
addedLangs->push_back( res.getLanguageName() );
|
||||
@@ -1111,6 +1114,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream,
|
||||
res.mLanguageIndex = mDefinitions.size();
|
||||
mDefinitions.emplace_back(
|
||||
std::make_shared<SyntaxDefinition>( std::move( res ) ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1125,6 +1129,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream,
|
||||
Lock l( mMutex );
|
||||
mDefinitions[pos.value()] =
|
||||
std::make_shared<SyntaxDefinition>( std::move( res ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
} else {
|
||||
if ( addedLangs )
|
||||
addedLangs->push_back( res.getLanguageName() );
|
||||
@@ -1132,6 +1137,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream,
|
||||
res.mLanguageIndex = mDefinitions.size();
|
||||
mDefinitions.emplace_back(
|
||||
std::make_shared<SyntaxDefinition>( std::move( res ) ) );
|
||||
mExtensionManyLanguagesCache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1259,55 +1265,52 @@ bool SyntaxDefinitionManager::extensionCanRepresentManyLanguages( std::string ex
|
||||
if ( extension[0] != '.' )
|
||||
extension = '.' + extension;
|
||||
|
||||
std::unordered_set<std::string> count;
|
||||
{
|
||||
Lock l( mMutex );
|
||||
for ( const auto& definition : mDefinitions ) {
|
||||
for ( const auto& ext : definition->getFiles() ) {
|
||||
if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) ||
|
||||
String::endsWith( ext, "$" ) ) {
|
||||
LuaPattern words( ext );
|
||||
int start, end;
|
||||
if ( words.find( extension, start, end ) ) {
|
||||
count.insert( definition->getLanguageName() );
|
||||
if ( count.size() > 1 )
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else if ( extension == ext ) {
|
||||
count.insert( definition->getLanguageName() );
|
||||
if ( count.size() > 1 )
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
Lock l( mMutex );
|
||||
if ( auto it = mExtensionManyLanguagesCache.find( extension );
|
||||
it != mExtensionManyLanguagesCache.end() )
|
||||
return it->second;
|
||||
|
||||
auto supportsExtension = [&extension]( const std::vector<std::string>& files ) {
|
||||
for ( const auto& ext : files ) {
|
||||
if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) ||
|
||||
String::endsWith( ext, "$" ) ) {
|
||||
LuaPattern pattern( ext );
|
||||
int start, end;
|
||||
if ( pattern.find( extension, start, end ) )
|
||||
return true;
|
||||
} else if ( extension == ext ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const std::string* matchedLanguage = nullptr;
|
||||
auto foundDifferentLanguage = [&matchedLanguage]( const std::string& language ) {
|
||||
if ( !matchedLanguage ) {
|
||||
matchedLanguage = &language;
|
||||
return false;
|
||||
}
|
||||
return *matchedLanguage != language;
|
||||
};
|
||||
|
||||
for ( const auto& definition : mDefinitions ) {
|
||||
if ( supportsExtension( definition->getFiles() ) &&
|
||||
foundDifferentLanguage( definition->getLanguageName() ) ) {
|
||||
mExtensionManyLanguagesCache.emplace( extension, true );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Lock l( mMutex );
|
||||
for ( const auto& preDefinition : mPreDefinitions ) {
|
||||
for ( const auto& ext : preDefinition.getFiles() ) {
|
||||
if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) ||
|
||||
String::endsWith( ext, "$" ) ) {
|
||||
LuaPattern words( ext );
|
||||
int start, end;
|
||||
if ( words.find( extension, start, end ) ) {
|
||||
count.insert( preDefinition.getLanguageName() );
|
||||
if ( count.size() > 1 )
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else if ( extension == ext ) {
|
||||
count.insert( preDefinition.getLanguageName() );
|
||||
if ( count.size() > 1 )
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for ( const auto& preDefinition : mPreDefinitions ) {
|
||||
if ( supportsExtension( preDefinition.getFiles() ) &&
|
||||
foundDifferentLanguage( preDefinition.getLanguageName() ) ) {
|
||||
mExtensionManyLanguagesCache.emplace( extension, true );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
mExtensionManyLanguagesCache.emplace( std::move( extension ), false );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -302,10 +302,12 @@ bool TextDocument::fileMightBeBinary( const std::string& file ) {
|
||||
}
|
||||
|
||||
bool TextDocument::isTextDocumentCommand( std::string_view cmd ) {
|
||||
(void)getBuiltinCommands();
|
||||
return TEXT_DOCUMENT_COMMANDS.contains( String::hash( cmd ) );
|
||||
}
|
||||
|
||||
bool TextDocument::isTextDocumentCommand( String::HashType cmdHash ) {
|
||||
(void)getBuiltinCommands();
|
||||
return TEXT_DOCUMENT_COMMANDS.contains( cmdHash );
|
||||
}
|
||||
|
||||
@@ -1475,7 +1477,16 @@ std::string TextDocument::toUtf8String() {
|
||||
|
||||
std::vector<std::string> TextDocument::getCommandList() const {
|
||||
std::vector<std::string> cmds;
|
||||
cmds.reserve( mCommands.size() + mRefCommands.size() );
|
||||
cmds.reserve( getBuiltinCommands().size() + mCommands.size() + mRefCommands.size() +
|
||||
( mSharedRefCommands ? mSharedRefCommands->size() : 0 ) );
|
||||
for ( const auto& cmd : getBuiltinCommands() ) {
|
||||
if ( !isDefaultCommandRemoved( cmd.first ) )
|
||||
cmds.push_back( cmd.first );
|
||||
}
|
||||
if ( mSharedRefCommands ) {
|
||||
for ( const auto& cmd : *mSharedRefCommands )
|
||||
cmds.push_back( cmd.first );
|
||||
}
|
||||
for ( const auto& cmd : mRefCommands )
|
||||
cmds.push_back( cmd.first );
|
||||
for ( const auto& cmd : mCommands )
|
||||
@@ -3205,25 +3216,42 @@ bool TextDocument::isDirty() const {
|
||||
void TextDocument::execute( const std::string& command ) {
|
||||
auto cmdIt = mCommands.find( command );
|
||||
if ( cmdIt != mCommands.end() )
|
||||
cmdIt->second();
|
||||
return cmdIt->second();
|
||||
auto builtinIt = getBuiltinCommands().find( command );
|
||||
if ( builtinIt != getBuiltinCommands().end() && !isDefaultCommandRemoved( command ) )
|
||||
builtinIt->second( this );
|
||||
}
|
||||
|
||||
void TextDocument::execute( const std::string& command, Client* client ) {
|
||||
auto cmdRefIt = mRefCommands.find( command );
|
||||
if ( cmdRefIt != mRefCommands.end() )
|
||||
return cmdRefIt->second( client );
|
||||
if ( mSharedRefCommands ) {
|
||||
auto sharedCmdIt = mSharedRefCommands->find( command );
|
||||
if ( sharedCmdIt != mSharedRefCommands->end() )
|
||||
return sharedCmdIt->second( client );
|
||||
}
|
||||
auto cmdIt = mCommands.find( command );
|
||||
if ( cmdIt != mCommands.end() )
|
||||
return cmdIt->second();
|
||||
auto builtinIt = getBuiltinCommands().find( command );
|
||||
if ( builtinIt != getBuiltinCommands().end() && !isDefaultCommandRemoved( command ) )
|
||||
builtinIt->second( this );
|
||||
}
|
||||
|
||||
void TextDocument::setCommands( const UnorderedMap<std::string, DocumentCommand>& cmds ) {
|
||||
mCommands.insert( cmds.begin(), cmds.end() );
|
||||
if ( mRemovedDefaultCommands ) {
|
||||
for ( const auto& cmd : cmds )
|
||||
mRemovedDefaultCommands->erase( cmd.first );
|
||||
}
|
||||
}
|
||||
|
||||
void TextDocument::setCommand( const std::string& command,
|
||||
const TextDocument::DocumentCommand& func ) {
|
||||
mCommands[command] = func;
|
||||
if ( mRemovedDefaultCommands )
|
||||
mRemovedDefaultCommands->erase( command );
|
||||
}
|
||||
|
||||
void TextDocument::setCommand( const std::string& command,
|
||||
@@ -3231,13 +3259,43 @@ void TextDocument::setCommand( const std::string& command,
|
||||
mRefCommands[command] = func;
|
||||
}
|
||||
|
||||
void TextDocument::setSharedRefCommands(
|
||||
std::shared_ptr<const TextDocument::DocumentRefCommands> commands ) {
|
||||
mSharedRefCommands = std::move( commands );
|
||||
}
|
||||
|
||||
bool TextDocument::hasCommand( const std::string& command ) {
|
||||
return mCommands.find( command ) != mCommands.end() ||
|
||||
mRefCommands.find( command ) != mRefCommands.end();
|
||||
mRefCommands.find( command ) != mRefCommands.end() ||
|
||||
( mSharedRefCommands &&
|
||||
mSharedRefCommands->find( command ) != mSharedRefCommands->end() ) ||
|
||||
( getBuiltinCommands().find( command ) != getBuiltinCommands().end() &&
|
||||
!isDefaultCommandRemoved( command ) );
|
||||
}
|
||||
|
||||
bool TextDocument::removeCommand( const std::string& command ) {
|
||||
return mCommands.erase( command ) > 0 || mRefCommands.erase( command ) > 0;
|
||||
if ( mCommands.erase( command ) > 0 ) {
|
||||
if ( getBuiltinCommands().find( command ) != getBuiltinCommands().end() ) {
|
||||
if ( !mRemovedDefaultCommands )
|
||||
mRemovedDefaultCommands = std::make_unique<UnorderedSet<std::string>>();
|
||||
mRemovedDefaultCommands->insert( command );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ( mRefCommands.erase( command ) > 0 )
|
||||
return true;
|
||||
if ( getBuiltinCommands().find( command ) != getBuiltinCommands().end() &&
|
||||
!isDefaultCommandRemoved( command ) ) {
|
||||
if ( !mRemovedDefaultCommands )
|
||||
mRemovedDefaultCommands = std::make_unique<UnorderedSet<std::string>>();
|
||||
mRemovedDefaultCommands->insert( command );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextDocument::isDefaultCommandRemoved( const std::string& command ) const {
|
||||
return mRemovedDefaultCommands && mRemovedDefaultCommands->contains( command );
|
||||
}
|
||||
|
||||
static constexpr auto MAX_CAPTURES = 12;
|
||||
@@ -4885,88 +4943,105 @@ void TextDocument::clearIndentation() {
|
||||
}
|
||||
}
|
||||
|
||||
void TextDocument::initializeCommands() {
|
||||
// The built-in document commands and editor-specific commands share this table. Reserve their
|
||||
// known steady-state capacity so every new editor does not repeatedly grow and rehash it.
|
||||
mCommands.reserve( 128 );
|
||||
mCommands["reset-document"] = [this] { reset(); };
|
||||
mCommands["save-doc"] = [this] { save(); };
|
||||
mCommands["delete-to-previous-word"] = [this] { deleteToPreviousWord(); };
|
||||
mCommands["delete-to-previous-char"] = [this] { deleteToPreviousChar(); };
|
||||
mCommands["delete-to-next-word"] = [this] { deleteToNextWord(); };
|
||||
mCommands["delete-to-next-char"] = [this] { deleteToNextChar(); };
|
||||
mCommands["delete-current-line"] = [this] { deleteCurrentLine(); };
|
||||
mCommands["delete-to-start-of-line"] = [this] { deleteToStartOfLine(); };
|
||||
mCommands["delete-to-end-of-line"] = [this] { deleteToEndOfLine(); };
|
||||
mCommands["delete-selection"] = [this] { deleteSelection(); };
|
||||
mCommands["delete-word"] = [this] { deleteWord(); };
|
||||
mCommands["delete-paragraph"] = [this] { deleteCurrentParagraph(); };
|
||||
mCommands["move-to-previous-char"] = [this] { moveToPreviousChar(); };
|
||||
mCommands["move-to-previous-word"] = [this] { moveToPreviousWord(); };
|
||||
mCommands["move-to-next-char"] = [this] { moveToNextChar(); };
|
||||
mCommands["move-to-next-word"] = [this] { moveToNextWord(); };
|
||||
mCommands["move-to-previous-line"] = [this] { moveToPreviousLine(); };
|
||||
mCommands["move-to-next-line"] = [this] { moveToNextLine(); };
|
||||
mCommands["move-to-previous-page"] = [this] { moveToPreviousPage( mPageSize ); };
|
||||
mCommands["move-to-next-page"] = [this] { moveToNextPage( mPageSize ); };
|
||||
mCommands["move-to-start-of-doc"] = [this] { moveToStartOfDoc(); };
|
||||
mCommands["move-to-end-of-doc"] = [this] { moveToEndOfDoc(); };
|
||||
mCommands["move-to-start-of-line"] = [this] { moveToStartOfLine(); };
|
||||
mCommands["move-to-end-of-line"] = [this] { moveToEndOfLine(); };
|
||||
mCommands["move-to-start-of-content"] = [this] { moveToStartOfContent(); };
|
||||
mCommands["move-to-previous-paragraph"] = [this] { moveToPreviousParagraph(); };
|
||||
mCommands["move-to-next-paragraph"] = [this] { moveToNextParagraph(); };
|
||||
mCommands["move-lines-up"] = [this] { moveLinesUp(); };
|
||||
mCommands["move-lines-down"] = [this] { moveLinesDown(); };
|
||||
mCommands["select-to-previous-char"] = [this] { selectToPreviousChar(); };
|
||||
mCommands["select-to-previous-word"] = [this] { selectToPreviousWord(); };
|
||||
mCommands["select-to-previous-line"] = [this] { selectToPreviousLine(); };
|
||||
mCommands["select-to-next-char"] = [this] { selectToNextChar(); };
|
||||
mCommands["select-to-next-word"] = [this] { selectToNextWord(); };
|
||||
mCommands["select-to-next-line"] = [this] { selectToNextLine(); };
|
||||
mCommands["select-word"] = [this] { selectWord(); };
|
||||
mCommands["select-all-words"] = [this] { selectAllWords(); };
|
||||
mCommands["select-line"] = [this] { selectLine(); };
|
||||
mCommands["select-single-line"] = [this] { selectSingleLine(); };
|
||||
mCommands["select-to-start-of-line"] = [this] { selectToStartOfLine(); };
|
||||
mCommands["select-to-end-of-line"] = [this] { selectToEndOfLine(); };
|
||||
mCommands["select-to-start-of-doc"] = [this] { selectToStartOfDoc(); };
|
||||
mCommands["select-to-start-of-content"] = [this] { selectToStartOfContent(); };
|
||||
mCommands["select-to-end-of-doc"] = [this] { selectToEndOfDoc(); };
|
||||
mCommands["select-to-previous-page"] = [this] { selectToPreviousPage( mPageSize ); };
|
||||
mCommands["select-to-next-page"] = [this] { selectToNextPage( mPageSize ); };
|
||||
mCommands["select-paragraph"] = [this] { selectCurrentParagraph(); };
|
||||
mCommands["select-all"] = [this] { selectAll(); };
|
||||
mCommands["new-line"] = [this] { newLine(); };
|
||||
mCommands["new-line-above"] = [this] { newLineAbove(); };
|
||||
mCommands["indent"] = [this] { indent(); };
|
||||
mCommands["unindent"] = [this] { unindent(); };
|
||||
mCommands["undo"] = [this] { undo(); };
|
||||
mCommands["redo"] = [this] { redo(); };
|
||||
mCommands["toggle-line-comments"] = [this] { toggleLineComments(); };
|
||||
mCommands["toggle-block-comments"] = [this] { toggleBlockComments(); };
|
||||
mCommands["selection-to-upper"] = [this] { toUpperSelection(); };
|
||||
mCommands["selection-to-lower"] = [this] { toLowerSelection(); };
|
||||
mCommands["reset-cursor"] = [this] { resetSelection(); };
|
||||
mCommands["add-cursor-above"] = [this] { addCursorAbove(); };
|
||||
mCommands["add-cursor-below"] = [this] { addCursorBelow(); };
|
||||
mCommands["cursor-undo"] = [this] { cursorUndo(); };
|
||||
mCommands["select-all-matches"] = [this] { selectAllMatches(); };
|
||||
mCommands["escape"] = [this] { escape(); };
|
||||
mCommands["unescape"] = [this] { unescape(); };
|
||||
mCommands["to-base64"] = [this] { toBase64(); };
|
||||
mCommands["from-base64"] = [this] { fromBase64(); };
|
||||
mCommands["trim-trailing-whitespace"] = [this] { trimTrailingWhitespace(); };
|
||||
mCommands["join-lines"] = [this] { joinLines(); };
|
||||
mCommands["duplicate-line-or-selection"] = [this] { duplicateLineOrSelection(); };
|
||||
mCommands["convert-indentation-to-tabs"] = [this] { convertIndentationToTabs(); };
|
||||
mCommands["convert-indentation-to-spaces"] = [this] { convertIndentationToSpaces(); };
|
||||
mCommands["clear-indentation"] = [this] { clearIndentation(); };
|
||||
|
||||
if ( TEXT_DOCUMENT_COMMANDS.empty() ) {
|
||||
for ( const auto& [cmd, _] : mCommands )
|
||||
TEXT_DOCUMENT_COMMANDS.insert( String::hash( cmd ) );
|
||||
const UnorderedMap<std::string, TextDocument::BuiltinDocumentCommand>&
|
||||
TextDocument::getBuiltinCommands() {
|
||||
#define EE_TEXT_DOCUMENT_COMMAND( Name, Method ) \
|
||||
{ \
|
||||
Name, +[]( TextDocument* document ) { document->Method(); } \
|
||||
}
|
||||
static const UnorderedMap<std::string, BuiltinDocumentCommand> commands{
|
||||
EE_TEXT_DOCUMENT_COMMAND( "reset-document", reset ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "save-doc", save ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-previous-word", deleteToPreviousWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-previous-char", deleteToPreviousChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-next-word", deleteToNextWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-next-char", deleteToNextChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-current-line", deleteCurrentLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-start-of-line", deleteToStartOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-to-end-of-line", deleteToEndOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-selection", deleteSelection ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-word", deleteWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "delete-paragraph", deleteCurrentParagraph ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-char", moveToPreviousChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-word", moveToPreviousWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-next-char", moveToNextChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-next-word", moveToNextWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-line", moveToPreviousLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-next-line", moveToNextLine ),
|
||||
{ "move-to-previous-page",
|
||||
+[]( TextDocument* document ) { document->moveToPreviousPage( document->mPageSize ); } },
|
||||
{ "move-to-next-page",
|
||||
+[]( TextDocument* document ) { document->moveToNextPage( document->mPageSize ); } },
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-doc", moveToStartOfDoc ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-end-of-doc", moveToEndOfDoc ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-line", moveToStartOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-end-of-line", moveToEndOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-content", moveToStartOfContent ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-paragraph", moveToPreviousParagraph ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-to-next-paragraph", moveToNextParagraph ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-lines-up", moveLinesUp ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "move-lines-down", moveLinesDown ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-char", selectToPreviousChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-word", selectToPreviousWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-line", selectToPreviousLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-next-char", selectToNextChar ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-next-word", selectToNextWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-next-line", selectToNextLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-word", selectWord ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-all-words", selectAllWords ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-line", selectLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-single-line", selectSingleLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-line", selectToStartOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-end-of-line", selectToEndOfLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-doc", selectToStartOfDoc ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-content", selectToStartOfContent ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-to-end-of-doc", selectToEndOfDoc ),
|
||||
{ "select-to-previous-page",
|
||||
+[]( TextDocument* document ) {
|
||||
document->selectToPreviousPage( document->mPageSize );
|
||||
} },
|
||||
{ "select-to-next-page",
|
||||
+[]( TextDocument* document ) { document->selectToNextPage( document->mPageSize ); } },
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-paragraph", selectCurrentParagraph ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-all", selectAll ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "new-line", newLine ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "new-line-above", newLineAbove ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "indent", indent ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "unindent", unindent ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "undo", undo ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "redo", redo ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "toggle-line-comments", toggleLineComments ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "toggle-block-comments", toggleBlockComments ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "selection-to-upper", toUpperSelection ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "selection-to-lower", toLowerSelection ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "reset-cursor", resetSelection ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "add-cursor-above", addCursorAbove ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "add-cursor-below", addCursorBelow ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "cursor-undo", cursorUndo ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "select-all-matches", selectAllMatches ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "escape", escape ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "unescape", unescape ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "to-base64", toBase64 ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "from-base64", fromBase64 ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "trim-trailing-whitespace", trimTrailingWhitespace ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "join-lines", joinLines ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "duplicate-line-or-selection", duplicateLineOrSelection ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "convert-indentation-to-tabs", convertIndentationToTabs ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "convert-indentation-to-spaces", convertIndentationToSpaces ),
|
||||
EE_TEXT_DOCUMENT_COMMAND( "clear-indentation", clearIndentation ),
|
||||
};
|
||||
#undef EE_TEXT_DOCUMENT_COMMAND
|
||||
static const bool commandHashesInitialized = [] {
|
||||
for ( const auto& [command, _] : commands )
|
||||
TEXT_DOCUMENT_COMMANDS.insert( String::hash( command ) );
|
||||
return true;
|
||||
}();
|
||||
(void)commandHashesInitialized;
|
||||
return commands;
|
||||
}
|
||||
|
||||
void TextDocument::initializeCommands() {
|
||||
(void)getBuiltinCommands();
|
||||
}
|
||||
|
||||
size_t TextDocument::getTopMostCursorIndex() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <algorithm>
|
||||
#include <eepp/ui/keyboardshortcut.hpp>
|
||||
#include <eepp/window/input.hpp>
|
||||
|
||||
@@ -21,7 +22,32 @@ KeyBindings::Shortcut KeyBindings::sanitizeShortcut( const KeyBindings::Shortcut
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
KeyBindings::KeyBindings( const Window::Input* input ) : mInput( input ) {}
|
||||
std::vector<KeyBindings::Shortcut>
|
||||
KeyBindings::getOrderedShortcuts( const KeyBindings::ShortcutMap& bindings ) {
|
||||
std::vector<Shortcut> shortcuts;
|
||||
shortcuts.reserve( bindings.size() );
|
||||
for ( const auto& binding : bindings )
|
||||
shortcuts.emplace_back( binding.first );
|
||||
std::sort( shortcuts.begin(), shortcuts.end() );
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
std::shared_ptr<KeyBindings::Storage> KeyBindings::getEmptyStorage() {
|
||||
static auto storage = std::make_shared<Storage>();
|
||||
return storage;
|
||||
}
|
||||
|
||||
KeyBindings::KeyBindings( const Window::Input* input ) :
|
||||
mInput( input ), mStorage( getEmptyStorage() ) {}
|
||||
|
||||
void KeyBindings::setKeybinds( const KeyBindings& bindings ) {
|
||||
mStorage = bindings.mStorage;
|
||||
}
|
||||
|
||||
void KeyBindings::ensureUniqueStorage() {
|
||||
if ( !mStorage.unique() )
|
||||
mStorage = std::make_shared<Storage>( *mStorage );
|
||||
}
|
||||
|
||||
void KeyBindings::addKeybindsString( const std::map<std::string, std::string>& binds ) {
|
||||
for ( auto& bind : binds ) {
|
||||
@@ -29,9 +55,9 @@ void KeyBindings::addKeybindsString( const std::map<std::string, std::string>& b
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindings::addKeybinds( const std::map<KeyBindings::Shortcut, std::string>& binds ) {
|
||||
for ( auto& bind : binds ) {
|
||||
addKeybind( bind.first, bind.second );
|
||||
void KeyBindings::addKeybinds( const KeyBindings::ShortcutMap& binds ) {
|
||||
for ( const auto& shortcut : getOrderedShortcuts( binds ) ) {
|
||||
addKeybind( shortcut, binds.find( shortcut )->second );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +68,15 @@ void KeyBindings::addKeybindsStringUnordered(
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindings::addKeybindsUnordered(
|
||||
const std::unordered_map<KeyBindings::Shortcut, std::string>& binds ) {
|
||||
for ( auto& bind : binds ) {
|
||||
addKeybind( bind.first, bind.second );
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindings::addKeybindString( const std::string& key, const std::string& command ) {
|
||||
addKeybind( getShortcutFromString( key ), command );
|
||||
}
|
||||
|
||||
void KeyBindings::addKeybind( const KeyBindings::Shortcut& key, const std::string& command ) {
|
||||
mShortcuts[sanitizeShortcut( key )] = command;
|
||||
mKeybindingsInvert[command] = sanitizeShortcut( key );
|
||||
ensureUniqueStorage();
|
||||
auto shortcut = sanitizeShortcut( key );
|
||||
mStorage->shortcuts[shortcut] = command;
|
||||
mStorage->keybindingsInvert[command] = shortcut;
|
||||
}
|
||||
|
||||
void KeyBindings::replaceKeybindString( const std::string& keys, const std::string& command ) {
|
||||
@@ -63,18 +84,19 @@ void KeyBindings::replaceKeybindString( const std::string& keys, const std::stri
|
||||
}
|
||||
|
||||
void KeyBindings::replaceKeybind( const KeyBindings::Shortcut& keys, const std::string& command ) {
|
||||
ensureUniqueStorage();
|
||||
bool erased;
|
||||
do {
|
||||
erased = false;
|
||||
auto it = mShortcuts.find( sanitizeShortcut( keys ) );
|
||||
if ( it != mShortcuts.end() ) {
|
||||
mShortcuts.erase( it );
|
||||
mKeybindingsInvert.erase( it->second );
|
||||
auto it = mStorage->shortcuts.find( sanitizeShortcut( keys ) );
|
||||
if ( it != mStorage->shortcuts.end() ) {
|
||||
mStorage->shortcuts.erase( it );
|
||||
mStorage->keybindingsInvert.erase( it->second );
|
||||
erased = true;
|
||||
}
|
||||
} while ( erased );
|
||||
mShortcuts[sanitizeShortcut( keys )] = command;
|
||||
mKeybindingsInvert[command] = sanitizeShortcut( keys );
|
||||
mStorage->shortcuts[sanitizeShortcut( keys )] = command;
|
||||
mStorage->keybindingsInvert[command] = sanitizeShortcut( keys );
|
||||
}
|
||||
|
||||
KeyBindings::Shortcut KeyBindings::toShortcut( const Window::Input* input,
|
||||
@@ -102,9 +124,10 @@ KeyBindings::Shortcut KeyBindings::getShortcutFromString( const std::string& key
|
||||
}
|
||||
|
||||
void KeyBindings::removeKeybind( const KeyBindings::Shortcut& keys ) {
|
||||
auto it = mShortcuts.find( keys.toUint64() );
|
||||
if ( it != mShortcuts.end() ) {
|
||||
mShortcuts.erase( it );
|
||||
ensureUniqueStorage();
|
||||
auto it = mStorage->shortcuts.find( keys );
|
||||
if ( it != mStorage->shortcuts.end() ) {
|
||||
mStorage->shortcuts.erase( it );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,25 +136,26 @@ void KeyBindings::removeKeybind( const std::string& kb ) {
|
||||
}
|
||||
|
||||
bool KeyBindings::existsKeybind( const KeyBindings::Shortcut& keys ) {
|
||||
return mShortcuts.find( keys.toUint64() ) != mShortcuts.end();
|
||||
return mStorage->shortcuts.find( keys ) != mStorage->shortcuts.end();
|
||||
}
|
||||
|
||||
bool KeyBindings::hasCommand( const std::string& command ) {
|
||||
return mKeybindingsInvert.find( command ) != mKeybindingsInvert.end();
|
||||
return mStorage->keybindingsInvert.find( command ) != mStorage->keybindingsInvert.end();
|
||||
}
|
||||
|
||||
KeyBindings::Shortcut KeyBindings::getShortcutFromCommand( const std::string& cmd ) const {
|
||||
auto it = mKeybindingsInvert.find( cmd );
|
||||
if ( it != mKeybindingsInvert.end() )
|
||||
auto it = mStorage->keybindingsInvert.find( cmd );
|
||||
if ( it != mStorage->keybindingsInvert.end() )
|
||||
return it->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
void KeyBindings::removeCommandKeybind( const std::string& command ) {
|
||||
auto kbIt = mKeybindingsInvert.find( command );
|
||||
if ( kbIt != mKeybindingsInvert.end() ) {
|
||||
ensureUniqueStorage();
|
||||
auto kbIt = mStorage->keybindingsInvert.find( command );
|
||||
if ( kbIt != mStorage->keybindingsInvert.end() ) {
|
||||
removeKeybind( kbIt->second );
|
||||
mKeybindingsInvert.erase( command );
|
||||
mStorage->keybindingsInvert.erase( command );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,8 +165,8 @@ void KeyBindings::removeCommandsKeybind( const std::vector<std::string>& command
|
||||
}
|
||||
|
||||
std::string KeyBindings::getCommandFromKeyBind( const KeyBindings::Shortcut& keys ) {
|
||||
auto it = mShortcuts.find( sanitizeShortcut( keys ) );
|
||||
if ( it != mShortcuts.end() ) {
|
||||
auto it = mStorage->shortcuts.find( sanitizeShortcut( keys ) );
|
||||
if ( it != mStorage->shortcuts.end() ) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
@@ -166,23 +190,22 @@ std::string KeyBindings::keybindFormat( std::string str ) {
|
||||
}
|
||||
|
||||
std::string KeyBindings::getCommandKeybindString( const std::string& command ) const {
|
||||
auto it = mKeybindingsInvert.find( command );
|
||||
if ( it == mKeybindingsInvert.end() )
|
||||
auto it = mStorage->keybindingsInvert.find( command );
|
||||
if ( it == mStorage->keybindingsInvert.end() )
|
||||
return "";
|
||||
return keybindFormat( getShortcutString( Shortcut( it->second ) ) );
|
||||
return keybindFormat( getShortcutString( it->second ) );
|
||||
}
|
||||
|
||||
void KeyBindings::reset() {
|
||||
mShortcuts.clear();
|
||||
mKeybindingsInvert.clear();
|
||||
mStorage = getEmptyStorage();
|
||||
}
|
||||
|
||||
const KeyBindings::ShortcutMap& KeyBindings::getShortcutMap() const {
|
||||
return mShortcuts;
|
||||
return mStorage->shortcuts;
|
||||
}
|
||||
|
||||
const std::map<std::string, Uint64>& KeyBindings::getKeybindings() const {
|
||||
return mKeybindingsInvert;
|
||||
const std::map<std::string, KeyBindings::Shortcut>& KeyBindings::getKeybindings() const {
|
||||
return mStorage->keybindingsInvert;
|
||||
}
|
||||
|
||||
std::string KeyBindings::fromShortcut( const Window::Input* input, KeyBindings::Shortcut shortcut,
|
||||
|
||||
@@ -11,10 +11,11 @@ using namespace EE::System;
|
||||
|
||||
namespace EE { namespace UI { namespace Tools {
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string> UICodeEditorSplitter::getDefaultKeybindings() {
|
||||
const KeyBindings::ShortcutMap UICodeEditorSplitter::getDefaultKeybindings() {
|
||||
auto keybindings = UICodeEditor::getDefaultKeybindings();
|
||||
auto localKeybindings = getLocalDefaultKeybindings();
|
||||
localKeybindings.insert( keybindings.begin(), keybindings.end() );
|
||||
for ( const auto& [shortcut, command] : keybindings->getShortcutMap() )
|
||||
localKeybindings.emplace( shortcut, command );
|
||||
return localKeybindings;
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ Uint32 UICodeEditorSplitter::getDefaultSwitchToTabModifier() {
|
||||
return DefaultSwitchToTabModifier;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>
|
||||
const KeyBindings::ShortcutMap
|
||||
UICodeEditorSplitter::getLocalDefaultKeybindings() {
|
||||
return {
|
||||
{ { KEY_S, KeyMod::getDefaultModifier() }, "save-doc" },
|
||||
|
||||
@@ -25,6 +25,21 @@
|
||||
|
||||
namespace EE { namespace UI { namespace Tools {
|
||||
|
||||
struct UIDiffView::PreparedPatch {
|
||||
std::vector<DiffLine> lines;
|
||||
std::string filename;
|
||||
std::string binaryOldPath;
|
||||
std::string binaryNewPath;
|
||||
std::string binaryFileName;
|
||||
bool hasCompleteFile{ false };
|
||||
bool isBinaryImage{ false };
|
||||
};
|
||||
|
||||
class UIDiffView::PreparedMultiFileDiff {
|
||||
public:
|
||||
std::vector<PreparedPatch> patches;
|
||||
};
|
||||
|
||||
static bool imagesHaveSameDimensions( const std::string& oldFilePath,
|
||||
const std::string& newFilePath ) {
|
||||
int oldWidth = 0;
|
||||
@@ -80,8 +95,19 @@ static Sprite* setImageViewerImage( UIImageViewer* viewer, Image* image ) {
|
||||
}
|
||||
|
||||
UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText,
|
||||
const std::string& repoPath, ViewMode viewMode ) {
|
||||
auto diffs = UIDiffView::splitDiff( patchText );
|
||||
const std::string& repoPath, ViewMode viewMode,
|
||||
bool interactiveFileHeaders ) {
|
||||
return NewMultiFileDiffViewer( prepareMultiFileDiff( patchText ), repoPath, viewMode,
|
||||
interactiveFileHeaders );
|
||||
}
|
||||
|
||||
UIScrollView*
|
||||
UIDiffView::NewMultiFileDiffViewer( std::shared_ptr<PreparedMultiFileDiff> preparedDiff,
|
||||
const std::string& repoPath, ViewMode viewMode,
|
||||
bool interactiveFileHeaders ) {
|
||||
if ( !preparedDiff )
|
||||
return nullptr;
|
||||
|
||||
auto* uiSceneNode = SceneManager::instance()->getUISceneNode();
|
||||
const bool wasLoading = uiSceneNode && uiSceneNode->isLoading();
|
||||
if ( uiSceneNode )
|
||||
@@ -92,7 +118,7 @@ UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText,
|
||||
vbox->setParent( scrollView );
|
||||
vbox->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
|
||||
|
||||
for ( const auto& diff : diffs ) {
|
||||
for ( auto& patch : preparedDiff->patches ) {
|
||||
auto* diffView = UIDiffView::New();
|
||||
diffView->setViewMode( viewMode );
|
||||
diffView->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
|
||||
@@ -100,7 +126,8 @@ UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText,
|
||||
diffView->setHeadersVisible( true );
|
||||
diffView->setViewModeToggleVisible( false );
|
||||
diffView->setCompleteViewToggleVisible( false );
|
||||
diffView->loadFromPatch( diff, "", "", repoPath );
|
||||
diffView->setInteractiveFileHeader( interactiveFileHeaders );
|
||||
diffView->loadPreparedPatch( std::move( patch ), "", "", repoPath );
|
||||
}
|
||||
|
||||
if ( uiSceneNode ) {
|
||||
@@ -506,9 +533,8 @@ UIDiffView::UIDiffView() :
|
||||
mLeftEditor->setFontSize( mRightEditor->getFontSize() );
|
||||
mLeftPlugin->registerUpdate( mLeftEditor );
|
||||
} );
|
||||
mRightEditor->getVScrollBar()->on( Event::OnSizeChange, [this] ( auto ) {
|
||||
updateModeButton();
|
||||
} );
|
||||
mRightEditor->getVScrollBar()->on( Event::OnSizeChange,
|
||||
[this]( auto ) { updateModeButton(); } );
|
||||
|
||||
for ( auto* editor : { mEditor, mLeftEditor, mRightEditor } ) {
|
||||
editor->on( Event::OnSizeChange, [this]( auto ) { onAutoSize(); } );
|
||||
@@ -993,12 +1019,13 @@ void UIDiffView::updateImageDiffView() {
|
||||
}
|
||||
}
|
||||
|
||||
void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) {
|
||||
static void computeSubLineDiffImpl( UIDiffView::DiffLine& oldLine, UIDiffView::DiffLine& newLine,
|
||||
UIDiffView::SubLineDiffAlgorithm algorithm ) {
|
||||
dtl::Diff<String::StringBaseType, String::View> diff( oldLine.text.view(),
|
||||
newLine.text.view() );
|
||||
diff.compose();
|
||||
|
||||
if ( mSubLineDiffAlgorithm == SubLineDiffAlgorithm::SES ) {
|
||||
if ( algorithm == UIDiffView::SubLineDiffAlgorithm::SES ) {
|
||||
auto ses = diff.getSes().getSequence();
|
||||
Int64 oldIdx = 0;
|
||||
Int64 newIdx = 0;
|
||||
@@ -1068,11 +1095,18 @@ void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) {
|
||||
}
|
||||
}
|
||||
|
||||
static void applySubLineDiff(
|
||||
void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) {
|
||||
computeSubLineDiffImpl( oldLine, newLine, mSubLineDiffAlgorithm );
|
||||
}
|
||||
|
||||
static bool applySubLineDiff(
|
||||
std::vector<UIDiffView::DiffLine>& lines,
|
||||
std::function<void( UIDiffView::DiffLine&, UIDiffView::DiffLine& )> computeSubLineDiff ) {
|
||||
std::function<void( UIDiffView::DiffLine&, UIDiffView::DiffLine& )> computeSubLineDiff,
|
||||
const std::shared_ptr<std::atomic_bool>& cancelled = {} ) {
|
||||
size_t i = 0;
|
||||
while ( i < lines.size() ) {
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return false;
|
||||
if ( lines[i].type == UIDiffView::DiffLineType::Removed ) {
|
||||
size_t j = i;
|
||||
while ( j < lines.size() && lines[j].type == UIDiffView::DiffLineType::Removed )
|
||||
@@ -1085,6 +1119,8 @@ static void applySubLineDiff(
|
||||
size_t numAdded = k - j;
|
||||
size_t numToCompare = std::min( numRemoved, numAdded );
|
||||
for ( size_t m = 0; m < numToCompare; m++ ) {
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return false;
|
||||
computeSubLineDiff( lines[i + m], lines[j + m] );
|
||||
}
|
||||
i = k;
|
||||
@@ -1092,6 +1128,7 @@ static void applySubLineDiff(
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct BinaryImagePatch {
|
||||
@@ -1212,19 +1249,21 @@ static BinaryImagePatch parseBinaryImagePatch( const std::vector<std::string>& l
|
||||
return patch;
|
||||
}
|
||||
|
||||
void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& originalFilePath,
|
||||
const std::string& oldFilePath, const std::string& repoPath ) {
|
||||
resetToTextDiffView();
|
||||
mLines.clear();
|
||||
UIDiffView::PreparedPatch
|
||||
UIDiffView::preparePatch( const std::string& patchText, const std::string& originalFilePath,
|
||||
SubLineDiffAlgorithm algorithm,
|
||||
const std::shared_ptr<std::atomic_bool>& cancelled ) {
|
||||
PreparedPatch prepared;
|
||||
auto lines = String::split( patchText, '\n', true );
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return prepared;
|
||||
|
||||
std::string fileText;
|
||||
bool hasCompleteFile = false;
|
||||
std::vector<std::string> fileLines;
|
||||
if ( !originalFilePath.empty() && FileSystem::fileExists( originalFilePath ) ) {
|
||||
FileSystem::fileGet( originalFilePath, fileText );
|
||||
fileLines = String::split( fileText, '\n', true );
|
||||
hasCompleteFile = true;
|
||||
prepared.hasCompleteFile = true;
|
||||
}
|
||||
|
||||
Int64 oldLineNum = 0;
|
||||
@@ -1234,27 +1273,16 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
|
||||
std::string filename;
|
||||
|
||||
auto imagePatch = parseBinaryImagePatch( lines, originalFilePath );
|
||||
if ( imagePatch.isBinary ) {
|
||||
std::string oldImagePath( oldFilePath );
|
||||
if ( oldImagePath.empty() ) {
|
||||
oldImagePath =
|
||||
resolveImagePatchPath( imagePatch.oldPath, originalFilePath, false, repoPath );
|
||||
}
|
||||
std::string newImagePath(
|
||||
resolveImagePatchPath( imagePatch.newPath, originalFilePath, true, repoPath ) );
|
||||
prepared.isBinaryImage = imagePatch.isBinary;
|
||||
prepared.binaryOldPath = std::move( imagePatch.oldPath );
|
||||
prepared.binaryNewPath = std::move( imagePatch.newPath );
|
||||
prepared.binaryFileName = std::move( imagePatch.fileName );
|
||||
prepared.lines.reserve( lines.size() + fileLines.size() );
|
||||
|
||||
if ( oldImagePath == newImagePath )
|
||||
oldImagePath.clear();
|
||||
|
||||
if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) {
|
||||
if ( !imagePatch.fileName.empty() )
|
||||
mFileName = std::move( imagePatch.fileName );
|
||||
updateFileHeaderInfo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const auto& line : lines ) {
|
||||
for ( size_t lineIdx = 0; lineIdx < lines.size(); ++lineIdx ) {
|
||||
if ( ( lineIdx & 0xFF ) == 0 && cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return PreparedPatch{};
|
||||
const auto& line = lines[lineIdx];
|
||||
if ( String::startsWith( line, "diff " ) || String::startsWith( line, "index " ) ||
|
||||
String::startsWith( line, "--- " ) || String::startsWith( line, "+++ " ) ) {
|
||||
if ( String::startsWith( line, "+++ " ) ) {
|
||||
@@ -1270,27 +1298,33 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
|
||||
size_t minusPos = line.find( "-" );
|
||||
size_t plusPos = line.find( "+" );
|
||||
if ( minusPos != std::string::npos && plusPos != std::string::npos ) {
|
||||
auto parseLineNumber = [&line]( size_t start, size_t end, Int64& lineNumber ) {
|
||||
if ( end == std::string::npos || end <= start )
|
||||
return false;
|
||||
Int64 parsedLineNumber;
|
||||
if ( !String::fromString( parsedLineNumber, std::string_view{ line }.substr(
|
||||
start, end - start ) ) )
|
||||
return false;
|
||||
lineNumber = parsedLineNumber - 1;
|
||||
return true;
|
||||
};
|
||||
size_t commaPos = line.find( ",", minusPos );
|
||||
size_t spacePos = line.find( " ", minusPos );
|
||||
if ( commaPos != std::string::npos && commaPos < spacePos ) {
|
||||
oldLineNum =
|
||||
std::stoll( line.substr( minusPos + 1, commaPos - minusPos - 1 ) ) - 1;
|
||||
parseLineNumber( minusPos + 1, commaPos, oldLineNum );
|
||||
} else if ( spacePos != std::string::npos ) {
|
||||
oldLineNum =
|
||||
std::stoll( line.substr( minusPos + 1, spacePos - minusPos - 1 ) ) - 1;
|
||||
parseLineNumber( minusPos + 1, spacePos, oldLineNum );
|
||||
}
|
||||
|
||||
commaPos = line.find( ",", plusPos );
|
||||
spacePos = line.find( " ", plusPos );
|
||||
if ( commaPos != std::string::npos && commaPos < spacePos ) {
|
||||
newLineNum =
|
||||
std::stoll( line.substr( plusPos + 1, commaPos - plusPos - 1 ) ) - 1;
|
||||
parseLineNumber( plusPos + 1, commaPos, newLineNum );
|
||||
} else if ( spacePos != std::string::npos ) {
|
||||
newLineNum =
|
||||
std::stoll( line.substr( plusPos + 1, spacePos - plusPos - 1 ) ) - 1;
|
||||
parseLineNumber( plusPos + 1, spacePos, newLineNum );
|
||||
}
|
||||
|
||||
if ( hasCompleteFile ) {
|
||||
if ( prepared.hasCompleteFile ) {
|
||||
while ( expectedNewLineNum < newLineNum + 1 &&
|
||||
expectedNewLineNum <= (Int64)fileLines.size() ) {
|
||||
DiffLine dline;
|
||||
@@ -1298,7 +1332,7 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
|
||||
dline.text = fileLines[expectedNewLineNum - 1];
|
||||
dline.oldLineNum = expectedOldLineNum++;
|
||||
dline.newLineNum = expectedNewLineNum++;
|
||||
mLines.push_back( dline );
|
||||
prepared.lines.push_back( std::move( dline ) );
|
||||
}
|
||||
}
|
||||
expectedOldLineNum = oldLineNum + 1;
|
||||
@@ -1335,31 +1369,88 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
|
||||
expectedNewLineNum = newLineNum + 1;
|
||||
}
|
||||
|
||||
mLines.push_back( dline );
|
||||
prepared.lines.push_back( std::move( dline ) );
|
||||
}
|
||||
|
||||
if ( hasCompleteFile ) {
|
||||
if ( prepared.hasCompleteFile ) {
|
||||
while ( expectedNewLineNum <= (Int64)fileLines.size() ) {
|
||||
if ( ( expectedNewLineNum & 0xFF ) == 0 && cancelled &&
|
||||
cancelled->load( std::memory_order_relaxed ) )
|
||||
return PreparedPatch{};
|
||||
DiffLine dline;
|
||||
dline.type = DiffLineType::Common;
|
||||
dline.text = fileLines[expectedNewLineNum - 1];
|
||||
dline.oldLineNum = expectedOldLineNum++;
|
||||
dline.newLineNum = expectedNewLineNum++;
|
||||
mLines.push_back( dline );
|
||||
prepared.lines.push_back( std::move( dline ) );
|
||||
}
|
||||
}
|
||||
|
||||
applySubLineDiff( mLines, [this]( DiffLine& oldLine, DiffLine& newLine ) {
|
||||
computeSubLineDiff( oldLine, newLine );
|
||||
} );
|
||||
if ( !applySubLineDiff(
|
||||
prepared.lines,
|
||||
[algorithm]( DiffLine& oldLine, DiffLine& newLine ) {
|
||||
computeSubLineDiffImpl( oldLine, newLine, algorithm );
|
||||
},
|
||||
cancelled ) )
|
||||
return PreparedPatch{};
|
||||
|
||||
setCompleteViewToggleVisible( hasCompleteFile );
|
||||
prepared.filename = std::move( filename );
|
||||
return prepared;
|
||||
}
|
||||
|
||||
if ( !filename.empty() ) {
|
||||
auto def = SyntaxDefinitionManager::instance()->getByExtension( filename );
|
||||
std::shared_ptr<UIDiffView::PreparedMultiFileDiff>
|
||||
UIDiffView::prepareMultiFileDiff( const std::string& patchText,
|
||||
const std::shared_ptr<std::atomic_bool>& cancelled ) {
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return {};
|
||||
|
||||
auto diffs = splitDiff( patchText );
|
||||
auto prepared = std::make_shared<PreparedMultiFileDiff>();
|
||||
prepared->patches.reserve( diffs.size() );
|
||||
for ( const auto& diff : diffs ) {
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return {};
|
||||
prepared->patches.emplace_back(
|
||||
preparePatch( diff, "", SubLineDiffAlgorithm::LCS, cancelled ) );
|
||||
}
|
||||
|
||||
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
|
||||
return {};
|
||||
return prepared;
|
||||
}
|
||||
|
||||
void UIDiffView::loadPreparedPatch( PreparedPatch&& patch, const std::string& originalFilePath,
|
||||
const std::string& oldFilePath, const std::string& repoPath ) {
|
||||
resetToTextDiffView();
|
||||
mLines.clear();
|
||||
|
||||
if ( patch.isBinaryImage ) {
|
||||
std::string oldImagePath( oldFilePath );
|
||||
if ( oldImagePath.empty() ) {
|
||||
oldImagePath =
|
||||
resolveImagePatchPath( patch.binaryOldPath, originalFilePath, false, repoPath );
|
||||
}
|
||||
std::string newImagePath(
|
||||
resolveImagePatchPath( patch.binaryNewPath, originalFilePath, true, repoPath ) );
|
||||
if ( oldImagePath == newImagePath )
|
||||
oldImagePath.clear();
|
||||
|
||||
if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) {
|
||||
if ( !patch.binaryFileName.empty() )
|
||||
mFileName = String::fromUtf8( patch.binaryFileName );
|
||||
updateFileHeaderInfo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mLines = std::move( patch.lines );
|
||||
setCompleteViewToggleVisible( patch.hasCompleteFile );
|
||||
|
||||
if ( !patch.filename.empty() ) {
|
||||
auto def = SyntaxDefinitionManager::instance()->getByExtension( patch.filename );
|
||||
mSyntaxDef =
|
||||
SyntaxDefinitionManager::instance()->getLanguageDefinition( def.getLanguageIndex() );
|
||||
mFileName = std::move( filename );
|
||||
mFileName = String::fromUtf8( patch.filename );
|
||||
}
|
||||
updateFileHeaderInfo();
|
||||
|
||||
@@ -1368,6 +1459,12 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
|
||||
onSizeChange();
|
||||
}
|
||||
|
||||
void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& originalFilePath,
|
||||
const std::string& oldFilePath, const std::string& repoPath ) {
|
||||
loadPreparedPatch( preparePatch( patchText, originalFilePath, mSubLineDiffAlgorithm, {} ),
|
||||
originalFilePath, oldFilePath, repoPath );
|
||||
}
|
||||
|
||||
void UIDiffView::loadFromStrings( const std::string& oldText, const std::string& newText,
|
||||
const std::string& originalFilePath ) {
|
||||
resetToTextDiffView();
|
||||
|
||||
+84
-137
@@ -49,9 +49,7 @@ UICodeEditor* UICodeEditor::NewOpt( const bool& autoRegisterBaseCommands,
|
||||
return eeNew( UICodeEditor, ( autoRegisterBaseCommands, autoRegisterBaseKeybindings ) );
|
||||
}
|
||||
|
||||
using CodeEditorKeyBindingMap = std::map<KeyBindings::Shortcut, std::string>;
|
||||
|
||||
static CodeEditorKeyBindingMap createDefaultCodeEditorKeybindings() {
|
||||
static KeyBindings::ShortcutMap createDefaultCodeEditorKeybindings() {
|
||||
return {
|
||||
{ { KEY_BACKSPACE, KeyMod::getDefaultModifier() }, "delete-to-previous-word" },
|
||||
{ { KEY_BACKSPACE, KEYMOD_SHIFT }, "delete-to-previous-char" },
|
||||
@@ -132,12 +130,12 @@ static CodeEditorKeyBindingMap createDefaultCodeEditorKeybindings() {
|
||||
};
|
||||
}
|
||||
|
||||
static std::shared_ptr<const CodeEditorKeyBindingMap> getCachedDefaultCodeEditorKeybindings() {
|
||||
std::shared_ptr<const KeyBindings> UICodeEditor::getDefaultKeybindings() {
|
||||
struct Cache {
|
||||
Mutex mutex;
|
||||
Uint32 defaultModifier{ 0 };
|
||||
Uint32 secondaryModifier{ 0 };
|
||||
std::shared_ptr<const CodeEditorKeyBindingMap> bindings;
|
||||
std::shared_ptr<const KeyBindings> bindings;
|
||||
};
|
||||
static Cache cache;
|
||||
|
||||
@@ -146,18 +144,15 @@ static std::shared_ptr<const CodeEditorKeyBindingMap> getCachedDefaultCodeEditor
|
||||
Lock lock( cache.mutex );
|
||||
if ( !cache.bindings || cache.defaultModifier != defaultModifier ||
|
||||
cache.secondaryModifier != secondaryModifier ) {
|
||||
cache.bindings =
|
||||
std::make_shared<const CodeEditorKeyBindingMap>( createDefaultCodeEditorKeybindings() );
|
||||
auto bindings = std::make_shared<KeyBindings>( nullptr );
|
||||
bindings->addKeybinds( createDefaultCodeEditorKeybindings() );
|
||||
cache.bindings = std::move( bindings );
|
||||
cache.defaultModifier = defaultModifier;
|
||||
cache.secondaryModifier = secondaryModifier;
|
||||
}
|
||||
return cache.bindings;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string> UICodeEditor::getDefaultKeybindings() {
|
||||
return *getCachedDefaultCodeEditorKeybindings();
|
||||
}
|
||||
|
||||
const MouseBindings::ShortcutMap UICodeEditor::getDefaultMousebindings() {
|
||||
return { { { MouseAction::Down, EE_BUTTON_LMASK, KeyMod::getDefaultModifier() },
|
||||
"add-cursor-at-mouse-position" },
|
||||
@@ -2914,7 +2909,7 @@ void UICodeEditor::addKeyBindsString( const std::map<std::string, std::string>&
|
||||
}
|
||||
}
|
||||
|
||||
void UICodeEditor::addKeyBinds( const std::map<KeyBindings::Shortcut, std::string>& binds,
|
||||
void UICodeEditor::addKeyBinds( const KeyBindings::ShortcutMap& binds,
|
||||
const bool& allowLocked ) {
|
||||
mKeyBindings.addKeybinds( binds );
|
||||
for ( const auto& bind : binds ) {
|
||||
@@ -4853,129 +4848,81 @@ void UICodeEditor::drawLineEndings( const DocumentLineRange& lineRange, const Ve
|
||||
}
|
||||
}
|
||||
|
||||
void UICodeEditor::registerCommands() {
|
||||
mUnlockedCmd.reserve( 8 );
|
||||
mDoc->setCommand( "move-to-previous-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToPreviousLine();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-next-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToNextLine();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-previous-page", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToPreviousPage();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-next-page", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToNextPage();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-start-of-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToStartOfLine();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-end-of-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToEndOfLine();
|
||||
} );
|
||||
mDoc->setCommand( "move-to-start-of-content", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToStartOfContent();
|
||||
} );
|
||||
mDoc->setCommand( "select-to-previous-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->selectToPreviousLine();
|
||||
} );
|
||||
mDoc->setCommand( "select-to-next-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->selectToNextLine();
|
||||
} );
|
||||
mDoc->setCommand( "select-to-start-of-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->selectToStartOfLine();
|
||||
} );
|
||||
mDoc->setCommand( "select-to-end-of-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->selectToEndOfLine();
|
||||
} );
|
||||
mDoc->setCommand( "select-to-start-of-content", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->selectToStartOfContent();
|
||||
} );
|
||||
mDoc->setCommand( "move-scroll-up", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveScrollUp();
|
||||
} );
|
||||
mDoc->setCommand( "move-scroll-down", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveScrollDown();
|
||||
} );
|
||||
mDoc->setCommand( "jump-lines-up", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->jumpLinesUp();
|
||||
} );
|
||||
mDoc->setCommand( "jump-lines-down", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->jumpLinesDown();
|
||||
} );
|
||||
mDoc->setCommand( "indent",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->indent(); } );
|
||||
mDoc->setCommand( "unindent",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->unindent(); } );
|
||||
mDoc->setCommand( "copy",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->copy(); } );
|
||||
mDoc->setCommand( "cut",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->cut(); } );
|
||||
mDoc->setCommand( "paste",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->paste(); } );
|
||||
mDoc->setCommand( "font-size-grow", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->fontSizeGrow();
|
||||
} );
|
||||
mDoc->setCommand( "font-size-shrink", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->fontSizeShrink();
|
||||
} );
|
||||
mDoc->setCommand( "font-size-reset", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->fontSizeReset();
|
||||
} );
|
||||
mDoc->setCommand(
|
||||
"lock", []( Client* client ) { static_cast<UICodeEditor*>( client )->setLocked( true ); } );
|
||||
mDoc->setCommand( "unlock", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->setLocked( false );
|
||||
} );
|
||||
mDoc->setCommand( "lock-toggle", []( Client* client ) {
|
||||
UICodeEditor* editor = static_cast<UICodeEditor*>( client );
|
||||
editor->setLocked( !editor->isLocked() );
|
||||
} );
|
||||
mDoc->setCommand( "open-containing-folder", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->openContainingFolder();
|
||||
} );
|
||||
mDoc->setCommand( "copy-containing-folder-path", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->copyContainingFolderPath();
|
||||
} );
|
||||
mDoc->setCommand( "copy-file-path", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->copyFilePath();
|
||||
} );
|
||||
mDoc->setCommand( "copy-file-path-and-position", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->copyFilePath( true );
|
||||
} );
|
||||
mDoc->setCommand( "find-replace", [this] { showFindReplace(); } );
|
||||
mDoc->setCommand( "open-context-menu", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->createContextMenu();
|
||||
} );
|
||||
mDoc->setCommand( "add-cursor-at-mouse-position", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->addCursorAtMousePosition();
|
||||
} );
|
||||
mDoc->setCommand( "add-cursors-from-current-to-mouse-position", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->addCursorsFromCurrentToMousePosition();
|
||||
} );
|
||||
mDoc->setCommand( "toggle-fold", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->toggleFoldUnfold();
|
||||
} );
|
||||
mDoc->setCommand( "fold-all",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->foldAll(); } );
|
||||
mDoc->setCommand( "unfold-all",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->unfoldAll(); } );
|
||||
mDoc->setCommand( "fold",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->fold(); } );
|
||||
mDoc->setCommand( "unfold",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->unfold(); } );
|
||||
mDoc->setCommand( "open-hover-url", []( Client* client ) {
|
||||
UICodeEditor* editor = static_cast<UICodeEditor*>( client );
|
||||
if ( !editor->mLink.empty() )
|
||||
Engine::instance()->openURI( editor->mLink );
|
||||
} );
|
||||
mDoc->setCommand( "add-cursor-above", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->addCursorAbove();
|
||||
} );
|
||||
mDoc->setCommand( "add-cursor-below", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->addCursorBelow();
|
||||
} );
|
||||
std::shared_ptr<const TextDocument::DocumentRefCommands> UICodeEditor::getDefaultEditorCommands() {
|
||||
#define EE_CODE_EDITOR_COMMAND( Name, Method ) \
|
||||
{ \
|
||||
Name, []( Client* client ) { static_cast<UICodeEditor*>( client )->Method(); } \
|
||||
}
|
||||
static const auto commands = std::make_shared<const TextDocument::DocumentRefCommands>(
|
||||
TextDocument::DocumentRefCommands{
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-previous-line", moveToPreviousLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-next-line", moveToNextLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-previous-page", moveToPreviousPage ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-next-page", moveToNextPage ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-start-of-line", moveToStartOfLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-end-of-line", moveToEndOfLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-to-start-of-content", moveToStartOfContent ),
|
||||
EE_CODE_EDITOR_COMMAND( "select-to-previous-line", selectToPreviousLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "select-to-next-line", selectToNextLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "select-to-start-of-line", selectToStartOfLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "select-to-end-of-line", selectToEndOfLine ),
|
||||
EE_CODE_EDITOR_COMMAND( "select-to-start-of-content", selectToStartOfContent ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-scroll-up", moveScrollUp ),
|
||||
EE_CODE_EDITOR_COMMAND( "move-scroll-down", moveScrollDown ),
|
||||
EE_CODE_EDITOR_COMMAND( "jump-lines-up", jumpLinesUp ),
|
||||
EE_CODE_EDITOR_COMMAND( "jump-lines-down", jumpLinesDown ),
|
||||
EE_CODE_EDITOR_COMMAND( "indent", indent ),
|
||||
EE_CODE_EDITOR_COMMAND( "unindent", unindent ),
|
||||
EE_CODE_EDITOR_COMMAND( "copy", copy ),
|
||||
EE_CODE_EDITOR_COMMAND( "cut", cut ),
|
||||
EE_CODE_EDITOR_COMMAND( "paste", paste ),
|
||||
EE_CODE_EDITOR_COMMAND( "font-size-grow", fontSizeGrow ),
|
||||
EE_CODE_EDITOR_COMMAND( "font-size-shrink", fontSizeShrink ),
|
||||
EE_CODE_EDITOR_COMMAND( "font-size-reset", fontSizeReset ),
|
||||
{ "lock",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->setLocked( true ); } },
|
||||
{ "unlock",
|
||||
[]( Client* client ) { static_cast<UICodeEditor*>( client )->setLocked( false ); } },
|
||||
{ "lock-toggle",
|
||||
[]( Client* client ) {
|
||||
auto* editor = static_cast<UICodeEditor*>( client );
|
||||
editor->setLocked( !editor->isLocked() );
|
||||
} },
|
||||
EE_CODE_EDITOR_COMMAND( "open-containing-folder", openContainingFolder ),
|
||||
EE_CODE_EDITOR_COMMAND( "copy-containing-folder-path", copyContainingFolderPath ),
|
||||
EE_CODE_EDITOR_COMMAND( "copy-file-path", copyFilePath ),
|
||||
{ "copy-file-path-and-position",
|
||||
[]( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->copyFilePath( true );
|
||||
} },
|
||||
EE_CODE_EDITOR_COMMAND( "open-context-menu", createContextMenu ),
|
||||
EE_CODE_EDITOR_COMMAND( "add-cursor-at-mouse-position", addCursorAtMousePosition ),
|
||||
EE_CODE_EDITOR_COMMAND( "add-cursors-from-current-to-mouse-position",
|
||||
addCursorsFromCurrentToMousePosition ),
|
||||
EE_CODE_EDITOR_COMMAND( "toggle-fold", toggleFoldUnfold ),
|
||||
EE_CODE_EDITOR_COMMAND( "fold-all", foldAll ),
|
||||
EE_CODE_EDITOR_COMMAND( "unfold-all", unfoldAll ),
|
||||
EE_CODE_EDITOR_COMMAND( "fold", fold ),
|
||||
EE_CODE_EDITOR_COMMAND( "unfold", unfold ),
|
||||
{ "open-hover-url",
|
||||
[]( Client* client ) {
|
||||
auto* editor = static_cast<UICodeEditor*>( client );
|
||||
if ( !editor->mLink.empty() )
|
||||
Engine::instance()->openURI( editor->mLink );
|
||||
} },
|
||||
EE_CODE_EDITOR_COMMAND( "add-cursor-above", addCursorAbove ),
|
||||
EE_CODE_EDITOR_COMMAND( "add-cursor-below", addCursorBelow ),
|
||||
} );
|
||||
#undef EE_CODE_EDITOR_COMMAND
|
||||
return commands;
|
||||
}
|
||||
|
||||
void UICodeEditor::registerCommands() {
|
||||
mDoc->setSharedRefCommands( getDefaultEditorCommands() );
|
||||
// This is the only default editor command bound to a specific editor instance.
|
||||
mDoc->setCommand( "find-replace", [this] { showFindReplace(); } );
|
||||
|
||||
mUnlockedCmd.reserve( 8 );
|
||||
mUnlockedCmd.insert( { "copy", "select-all", "open-containing-folder",
|
||||
"copy-containing-folder-path", "copy-file-path",
|
||||
"copy-file-path-and-position", "open-context-menu", "find-replace" } );
|
||||
@@ -5007,9 +4954,9 @@ Tools::UIDocFindReplace* UICodeEditor::getFindReplace() {
|
||||
}
|
||||
|
||||
void UICodeEditor::registerKeybindings() {
|
||||
// Editors keep mutable bindings, but the immutable defaults only need to be constructed once
|
||||
// for each configured default-modifier pair.
|
||||
mKeyBindings.addKeybinds( *getCachedDefaultCodeEditorKeybindings() );
|
||||
// Bindings use copy-on-write storage, so unmodified editors share both lookup maps and only
|
||||
// allocate their own copy if an editor-specific binding is changed.
|
||||
mKeyBindings.setKeybinds( *getDefaultKeybindings() );
|
||||
mMouseBindings.addMousebinds( getDefaultMousebindings() );
|
||||
}
|
||||
|
||||
|
||||
@@ -2071,7 +2071,7 @@ void UISceneNode::addKeyBindsString( const std::map<std::string, std::string>& b
|
||||
mKeyBindings.addKeybindsString( binds );
|
||||
}
|
||||
|
||||
void UISceneNode::addKeyBinds( const std::map<KeyBindings::Shortcut, std::string>& binds ) {
|
||||
void UISceneNode::addKeyBinds( const KeyBindings::ShortcutMap& binds ) {
|
||||
mKeyBindings.addKeybinds( binds );
|
||||
}
|
||||
|
||||
|
||||
@@ -1804,7 +1804,7 @@ void UIWindow::addKeyBindsString( const std::map<std::string, std::string>& bind
|
||||
mKeyBindings.addKeybindsString( binds );
|
||||
}
|
||||
|
||||
void UIWindow::addKeyBinds( const std::map<KeyBindings::Shortcut, std::string>& binds ) {
|
||||
void UIWindow::addKeyBinds( const KeyBindings::ShortcutMap& binds ) {
|
||||
mKeyBindings.addKeybinds( binds );
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class UITerminal : public UIWidget {
|
||||
|
||||
void addKeyBindsString( const std::map<std::string, std::string>& binds );
|
||||
|
||||
void addKeyBinds( const std::map<KeyBindings::Shortcut, std::string>& binds );
|
||||
void addKeyBinds( const KeyBindings::ShortcutMap& binds );
|
||||
|
||||
bool execute( const std::string& command );
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ void UITerminal::addKeyBindsString( const std::map<std::string, std::string>& bi
|
||||
mKeyBindings.addKeybindsString( binds );
|
||||
}
|
||||
|
||||
void UITerminal::addKeyBinds( const std::map<KeyBindings::Shortcut, std::string>& binds ) {
|
||||
void UITerminal::addKeyBinds( const KeyBindings::ShortcutMap& binds ) {
|
||||
mKeyBindings.addKeybinds( binds );
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ int testTextRendering() {
|
||||
}
|
||||
|
||||
EE_MAIN_FUNC int main( int, char*[] ) {
|
||||
return testTextRendering();
|
||||
// return testTextRendering();
|
||||
|
||||
win = Engine::instance()->createWindow( WindowSettings( 1366, 768, "eepp - UI Perf Test" ) );
|
||||
|
||||
|
||||
@@ -8,6 +8,41 @@ using namespace EE;
|
||||
using namespace EE::UI;
|
||||
using namespace EE::Window;
|
||||
|
||||
UTEST( KeyboardShortcut, BulkBindingsPreserveLegacyOrderedReverseLookup ) {
|
||||
const KeyBindings::Shortcut first{ KEY_A, KEYMOD_NONE };
|
||||
const KeyBindings::Shortcut second{ KEY_B, KEYMOD_CTRL };
|
||||
const KeyBindings::Shortcut third{ KEY_C, KEYMOD_CTRL | KEYMOD_SHIFT };
|
||||
KeyBindings::ShortcutMap defaults;
|
||||
defaults.emplace( third, "duplicate-command" );
|
||||
defaults.emplace( first, "duplicate-command" );
|
||||
defaults.emplace( second, "duplicate-command" );
|
||||
|
||||
auto ordered = KeyBindings::getOrderedShortcuts( defaults );
|
||||
EXPECT_EQ( first.toUint64(), ordered.front().toUint64() );
|
||||
EXPECT_EQ( third.toUint64(), ordered.back().toUint64() );
|
||||
|
||||
KeyBindings bindings( nullptr );
|
||||
bindings.addKeybinds( defaults );
|
||||
EXPECT_EQ( third.toUint64(),
|
||||
bindings.getShortcutFromCommand( "duplicate-command" ).toUint64() );
|
||||
}
|
||||
|
||||
UTEST( KeyboardShortcut, SharedBindingsDetachOnMutation ) {
|
||||
KeyBindings defaults( nullptr );
|
||||
defaults.addKeybind( { KEY_A, KEYMOD_CTRL }, "select-all" );
|
||||
|
||||
KeyBindings bindings( nullptr );
|
||||
bindings.setKeybinds( defaults );
|
||||
EXPECT_TRUE( &bindings.getShortcutMap() == &defaults.getShortcutMap() );
|
||||
EXPECT_TRUE( bindings.getCommandFromKeyBind( { KEY_A, KEYMOD_CTRL } ) == "select-all" );
|
||||
|
||||
bindings.addKeybind( { KEY_C, KEYMOD_CTRL }, "copy" );
|
||||
EXPECT_TRUE( &bindings.getShortcutMap() != &defaults.getShortcutMap() );
|
||||
EXPECT_TRUE( bindings.getCommandFromKeyBind( { KEY_C, KEYMOD_CTRL } ) == "copy" );
|
||||
EXPECT_TRUE( defaults.getCommandFromKeyBind( { KEY_C, KEYMOD_CTRL } ).empty() );
|
||||
EXPECT_TRUE( defaults.getCommandFromKeyBind( { KEY_A, KEYMOD_CTRL } ) == "select-all" );
|
||||
}
|
||||
|
||||
UTEST( KeyboardShortcut, AggregateAltUsesPrimaryAltAndKeepsAltGrDistinct ) {
|
||||
UIApplication app( WindowSettings{ 320, 240, "eepp - keyboard shortcut tests" } );
|
||||
auto& bindings = app.getUI()->getKeyBindings();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "utest.hpp"
|
||||
#include <algorithm>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
@@ -6,6 +7,28 @@
|
||||
using namespace EE::UI::Doc;
|
||||
using namespace EE::System;
|
||||
|
||||
UTEST( TextDocument, BuiltinCommandsRemainAvailableWithoutPerDocumentClosures ) {
|
||||
TextDocument doc;
|
||||
doc.textInput( "content" );
|
||||
EXPECT_TRUE( doc.hasCommand( "select-all" ) );
|
||||
EXPECT_TRUE( doc.hasCommand( "delete-selection" ) );
|
||||
|
||||
doc.execute( "select-all" );
|
||||
doc.execute( "delete-selection" );
|
||||
EXPECT_TRUE( doc.isEmpty() );
|
||||
|
||||
auto commands = doc.getCommandList();
|
||||
EXPECT_TRUE( std::find( commands.begin(), commands.end(), "undo" ) != commands.end() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, RemovingBuiltinCommandIsLocalToDocument ) {
|
||||
TextDocument first;
|
||||
TextDocument second;
|
||||
EXPECT_TRUE( first.removeCommand( "undo" ) );
|
||||
EXPECT_FALSE( first.hasCommand( "undo" ) );
|
||||
EXPECT_TRUE( second.hasCommand( "undo" ) );
|
||||
}
|
||||
|
||||
UTEST( TextRanges, keepsCommonSelectionsInline ) {
|
||||
TextRanges ranges;
|
||||
EXPECT_TRUE( ranges.is_small() );
|
||||
|
||||
@@ -25,6 +25,43 @@ class TestableCodeEditor : public UICodeEditor {
|
||||
void clearLongestLineWidthDirtyForTest() { mLongestLineWidthDirty = false; }
|
||||
};
|
||||
|
||||
UTEST( SyntaxColorScheme, CopiesShareStorageAndDetachOnMutation ) {
|
||||
auto defaults = SyntaxColorScheme::getDefaultDark();
|
||||
auto copy = defaults;
|
||||
EXPECT_TRUE( &defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ) ==
|
||||
©.getSyntaxStyle( SyntaxStyleTypes::Keyword ) );
|
||||
|
||||
const auto defaultKeyword = defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color;
|
||||
copy.setSyntaxStyle( SyntaxStyleTypes::Keyword, SyntaxColorScheme::Style{ Color::Red } );
|
||||
EXPECT_TRUE( copy.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color == Color::Red );
|
||||
EXPECT_TRUE( defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color == defaultKeyword );
|
||||
EXPECT_TRUE( &defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ) !=
|
||||
©.getSyntaxStyle( SyntaxStyleTypes::Keyword ) );
|
||||
}
|
||||
|
||||
UTEST( SyntaxDefinitionManager, ManyLanguageExtensionCacheInvalidatesOnAdd ) {
|
||||
auto* manager = SyntaxDefinitionManager::instance();
|
||||
const std::string extension( ".eepp-many-languages-cache-test" );
|
||||
const std::string preDefinitionExtension( ".eepp-many-languages-predefinition-cache-test" );
|
||||
|
||||
EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( extension ) );
|
||||
manager->add( { "EEPP Cache Test A", { extension }, {} } );
|
||||
EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( extension ) );
|
||||
manager->add( { "EEPP Cache Test B", { extension }, {} } );
|
||||
EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( extension ) );
|
||||
EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( extension ) );
|
||||
|
||||
manager->add( { "EEPP Cache Test C", { preDefinitionExtension }, {} } );
|
||||
EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( preDefinitionExtension ) );
|
||||
manager->addPreDefinition( { "EEPP Cache Test PreDefinition",
|
||||
[]() -> SyntaxDefinition& {
|
||||
return SyntaxDefinitionManager::instance()->add(
|
||||
{ "EEPP Cache Test PreDefinition", {}, {} } );
|
||||
},
|
||||
{ preDefinitionExtension } } );
|
||||
EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( preDefinitionExtension ) );
|
||||
}
|
||||
|
||||
UTEST( MainThreadLifetime, InvalidatedCallbacksDoNotRun ) {
|
||||
UIApplication app( WindowSettings{ 320, 240, "eepp - main thread lifetime test" } );
|
||||
int owner = 42;
|
||||
@@ -101,25 +138,28 @@ UTEST( UICodeEditor, DefaultKeybindingCacheTracksConfiguredModifiers ) {
|
||||
const Uint32 originalSecondaryModifier = KeyMod::getDefaultSecondaryModifier();
|
||||
|
||||
auto defaultBindings = UICodeEditor::getDefaultKeybindings();
|
||||
auto copy = defaultBindings.find( { KEY_C, originalDefaultModifier } );
|
||||
EXPECT_TRUE( copy != defaultBindings.end() );
|
||||
if ( copy != defaultBindings.end() )
|
||||
const auto& defaultShortcutMap = defaultBindings->getShortcutMap();
|
||||
auto copy = defaultShortcutMap.find( KeyBindings::Shortcut{ KEY_C, originalDefaultModifier } );
|
||||
EXPECT_TRUE( copy != defaultShortcutMap.end() );
|
||||
if ( copy != defaultShortcutMap.end() )
|
||||
EXPECT_STREQ( "copy", copy->second.c_str() );
|
||||
|
||||
KeyMod::setDefaultModifier( KEYMOD_LALT );
|
||||
KeyMod::setDefaultSecondaryModifier( KEYMOD_META );
|
||||
auto reconfiguredBindings = UICodeEditor::getDefaultKeybindings();
|
||||
copy = reconfiguredBindings.find( { KEY_C, KEYMOD_LALT } );
|
||||
EXPECT_TRUE( copy != reconfiguredBindings.end() );
|
||||
if ( copy != reconfiguredBindings.end() )
|
||||
const auto& reconfiguredShortcutMap = reconfiguredBindings->getShortcutMap();
|
||||
copy = reconfiguredShortcutMap.find( KeyBindings::Shortcut{ KEY_C, KEYMOD_LALT } );
|
||||
EXPECT_TRUE( copy != reconfiguredShortcutMap.end() );
|
||||
if ( copy != reconfiguredShortcutMap.end() )
|
||||
EXPECT_STREQ( "copy", copy->second.c_str() );
|
||||
|
||||
KeyMod::setDefaultModifier( originalDefaultModifier );
|
||||
KeyMod::setDefaultSecondaryModifier( originalSecondaryModifier );
|
||||
auto restoredBindings = UICodeEditor::getDefaultKeybindings();
|
||||
copy = restoredBindings.find( { KEY_C, originalDefaultModifier } );
|
||||
EXPECT_TRUE( copy != restoredBindings.end() );
|
||||
if ( copy != restoredBindings.end() )
|
||||
const auto& restoredShortcutMap = restoredBindings->getShortcutMap();
|
||||
copy = restoredShortcutMap.find( KeyBindings::Shortcut{ KEY_C, originalDefaultModifier } );
|
||||
EXPECT_TRUE( copy != restoredShortcutMap.end() );
|
||||
if ( copy != restoredShortcutMap.end() )
|
||||
EXPECT_STREQ( "copy", copy->second.c_str() );
|
||||
}
|
||||
|
||||
@@ -236,7 +276,7 @@ UTEST( KeybindingsHelper, PreservesUserShortcutWhenAddingBinding ) {
|
||||
|
||||
std::unordered_map<std::string, std::string> keybindings;
|
||||
std::unordered_map<std::string, std::string> invertedKeybindings;
|
||||
const std::map<KeyBindings::Shortcut, std::string> defaultKeybindings{
|
||||
const KeyBindings::ShortcutMap defaultKeybindings{
|
||||
{ { KEY_D, KeyMod::getDefaultModifier() }, "select-word" },
|
||||
{ { KEY_X, KeyMod::getDefaultModifier() }, "cut" },
|
||||
};
|
||||
@@ -275,7 +315,7 @@ UTEST( KeybindingsHelper, RestoresMissingCommandWhenShortcutIsFree ) {
|
||||
|
||||
std::unordered_map<std::string, std::string> keybindings;
|
||||
std::unordered_map<std::string, std::string> invertedKeybindings;
|
||||
const std::map<KeyBindings::Shortcut, std::string> defaultKeybindings{
|
||||
const KeyBindings::ShortcutMap defaultKeybindings{
|
||||
{ { KEY_D, KeyMod::getDefaultModifier() }, "select-word" },
|
||||
{ { KEY_X, KeyMod::getDefaultModifier() }, "cut" },
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "utest.h"
|
||||
#include <atomic>
|
||||
#include <eepp/graphics/image.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
@@ -8,6 +9,7 @@
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uiscrollview.hpp>
|
||||
#include <thread>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
@@ -124,11 +126,15 @@ diff --git a/second.txt b/second.txt
|
||||
)patch";
|
||||
|
||||
auto* viewer =
|
||||
UIDiffView::NewMultiFileDiffViewer( patchText, "", UIDiffView::ViewMode::SideBySide );
|
||||
UIDiffView::NewMultiFileDiffViewer( patchText, "", UIDiffView::ViewMode::SideBySide, true );
|
||||
auto diffViews = viewer->findAllByType<UIDiffView>( UI_TYPE_DIFF_VIEW );
|
||||
ASSERT_EQ( size_t{ 2 }, diffViews.size() );
|
||||
for ( const auto* diffView : diffViews )
|
||||
for ( const auto* diffView : diffViews ) {
|
||||
EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffView->getViewMode() );
|
||||
EXPECT_FALSE( diffView->isViewModeToggleVisible() );
|
||||
EXPECT_FALSE( diffView->isCompleteViewToggleVisible() );
|
||||
EXPECT_TRUE( diffView->isInteractiveFileHeader() );
|
||||
}
|
||||
|
||||
UIDiffView::setMultiFileViewMode( viewer, UIDiffView::ViewMode::Unified );
|
||||
UIDiffView::setMultiFileCollapsed( viewer, true );
|
||||
@@ -146,6 +152,44 @@ diff --git a/second.txt b/second.txt
|
||||
eeDelete( viewer );
|
||||
}
|
||||
|
||||
UTEST( UIDiffView, PreparedMultiFileDiffBuildsViewer ) {
|
||||
std::string patchText = R"patch(diff --git a/first.txt b/first.txt
|
||||
--- a/first.txt
|
||||
+++ b/first.txt
|
||||
@@ -1 +1 @@
|
||||
-old
|
||||
+new
|
||||
diff --git a/second.txt b/second.txt
|
||||
--- a/second.txt
|
||||
+++ b/second.txt
|
||||
@@ -1 +1 @@
|
||||
-before
|
||||
+after
|
||||
)patch";
|
||||
|
||||
std::shared_ptr<UIDiffView::PreparedMultiFileDiff> prepared;
|
||||
std::thread worker(
|
||||
[&prepared, &patchText] { prepared = UIDiffView::prepareMultiFileDiff( patchText ); } );
|
||||
worker.join();
|
||||
ASSERT_TRUE( prepared );
|
||||
UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } );
|
||||
auto* viewer = UIDiffView::NewMultiFileDiffViewer( std::move( prepared ), "",
|
||||
UIDiffView::ViewMode::SideBySide );
|
||||
ASSERT_TRUE( viewer );
|
||||
auto diffViews = viewer->findAllByType<UIDiffView>( UI_TYPE_DIFF_VIEW );
|
||||
ASSERT_EQ( size_t{ 2 }, diffViews.size() );
|
||||
EXPECT_TRUE( diffViews[0]->getFileName().toUtf8() == "first.txt" );
|
||||
EXPECT_TRUE( diffViews[1]->getFileName().toUtf8() == "second.txt" );
|
||||
EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffViews[0]->getViewMode() );
|
||||
|
||||
eeDelete( viewer );
|
||||
}
|
||||
|
||||
UTEST( UIDiffView, PreparedMultiFileDiffHonorsCancellation ) {
|
||||
auto cancelled = std::make_shared<std::atomic_bool>( true );
|
||||
EXPECT_FALSE( UIDiffView::prepareMultiFileDiff( "diff --git a/a b/a\n", cancelled ) );
|
||||
}
|
||||
|
||||
UTEST( UIDiffView, MultiFileViewerHandlesLargePatches ) {
|
||||
UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } );
|
||||
app.getUI()->flushDirtyStyleAndLayout();
|
||||
|
||||
Vendored
+1
-1
Submodule src/thirdparty/efsw updated: 5df6a032e2...41ddf6822f
+25
-21
@@ -1231,15 +1231,15 @@ UITabWidget* App::getSidePanel() const {
|
||||
return mSidePanel;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& App::getRealLocalKeybindings() const {
|
||||
const KeyBindings::ShortcutMap& App::getRealLocalKeybindings() const {
|
||||
return mRealLocalKeybindings;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& App::getRealSplitterKeybindings() const {
|
||||
const KeyBindings::ShortcutMap& App::getRealSplitterKeybindings() const {
|
||||
return mRealSplitterKeybindings;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& App::getRealTerminalKeybindings() const {
|
||||
const KeyBindings::ShortcutMap& App::getRealTerminalKeybindings() const {
|
||||
return mRealTerminalKeybindings;
|
||||
}
|
||||
|
||||
@@ -1510,32 +1510,35 @@ void App::loadKeybindings() {
|
||||
getMigrateKeybindings(), mConfig.iniState );
|
||||
|
||||
auto localKeybindings = getLocalKeybindings();
|
||||
for ( const auto& kb : localKeybindings ) {
|
||||
auto found = mKeybindingsInvert.find( kb.second );
|
||||
for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localKeybindings ) ) {
|
||||
const auto& command = localKeybindings.find( shortcut )->second;
|
||||
auto found = mKeybindingsInvert.find( command );
|
||||
if ( found != mKeybindingsInvert.end() ) {
|
||||
mRealLocalKeybindings[bindings.getShortcutFromString( found->second )] = kb.second;
|
||||
mRealLocalKeybindings[bindings.getShortcutFromString( found->second )] = command;
|
||||
} else {
|
||||
mRealLocalKeybindings[kb.first] = kb.second;
|
||||
mRealLocalKeybindings[shortcut] = command;
|
||||
}
|
||||
}
|
||||
|
||||
auto localSplitterKeybindings = UICodeEditorSplitter::getLocalDefaultKeybindings();
|
||||
for ( const auto& kb : localSplitterKeybindings ) {
|
||||
auto found = mKeybindingsInvert.find( kb.second );
|
||||
for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localSplitterKeybindings ) ) {
|
||||
const auto& command = localSplitterKeybindings.find( shortcut )->second;
|
||||
auto found = mKeybindingsInvert.find( command );
|
||||
if ( found != mKeybindingsInvert.end() ) {
|
||||
mRealSplitterKeybindings[bindings.getShortcutFromString( found->second )] = kb.second;
|
||||
mRealSplitterKeybindings[bindings.getShortcutFromString( found->second )] = command;
|
||||
} else {
|
||||
mRealSplitterKeybindings[kb.first] = kb.second;
|
||||
mRealSplitterKeybindings[shortcut] = command;
|
||||
}
|
||||
}
|
||||
|
||||
auto localTerminalKeybindings = TerminalManager::getTerminalKeybindings();
|
||||
for ( const auto& kb : localTerminalKeybindings ) {
|
||||
auto found = mKeybindingsInvert.find( kb.second );
|
||||
for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localTerminalKeybindings ) ) {
|
||||
const auto& command = localTerminalKeybindings.find( shortcut )->second;
|
||||
auto found = mKeybindingsInvert.find( command );
|
||||
if ( found != mKeybindingsInvert.end() ) {
|
||||
mRealTerminalKeybindings[bindings.getShortcutFromString( found->second )] = kb.second;
|
||||
mRealTerminalKeybindings[bindings.getShortcutFromString( found->second )] = command;
|
||||
} else {
|
||||
mRealTerminalKeybindings[kb.first] = kb.second;
|
||||
mRealTerminalKeybindings[shortcut] = command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2100,7 +2103,7 @@ const AppConfig& App::getConfig() const {
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& App::getRealDefaultKeybindings() {
|
||||
const KeyBindings::ShortcutMap& App::getRealDefaultKeybindings() {
|
||||
if ( mRealDefaultKeybindings.empty() ) {
|
||||
mRealDefaultKeybindings.insert( mRealLocalKeybindings.begin(),
|
||||
mRealLocalKeybindings.end() );
|
||||
@@ -2112,7 +2115,7 @@ const std::map<KeyBindings::Shortcut, std::string>& App::getRealDefaultKeybindin
|
||||
return mRealDefaultKeybindings;
|
||||
}
|
||||
|
||||
std::map<KeyBindings::Shortcut, std::string> App::getDefaultKeybindings() {
|
||||
KeyBindings::ShortcutMap App::getDefaultKeybindings() {
|
||||
auto bindings = UICodeEditorSplitter::getDefaultKeybindings();
|
||||
auto local = getLocalKeybindings();
|
||||
auto app = TerminalManager::getTerminalKeybindings();
|
||||
@@ -2127,7 +2130,7 @@ static Uint32 DefaultSwitchToStatusPanelModifier = KeyMod::getDefaultModifier();
|
||||
static Uint32 DefaultSwitchToStatusPanelModifier = KeyMod::getDefaultSecondaryModifier();
|
||||
#endif
|
||||
|
||||
std::map<KeyBindings::Shortcut, std::string> App::getLocalKeybindings() {
|
||||
KeyBindings::ShortcutMap App::getLocalKeybindings() {
|
||||
return {
|
||||
{ { KEY_PRINTSCREEN, KEYMOD_NONE }, "take-screenshot" },
|
||||
{ { KEY_RETURN, KeyMod::getDefaultSecondaryModifier() | KeyMod::getDefaultModifier() },
|
||||
@@ -2626,15 +2629,16 @@ void App::loadAudioFromPath( const std::string& path, bool autoPlay ) {
|
||||
}
|
||||
|
||||
void App::loadDiffFromMemory( const std::string& content, const std::string& originalFilePath,
|
||||
const std::string& oldFilePath, const std::string& repoPath ) {
|
||||
const std::string& oldFilePath, const std::string& repoPath,
|
||||
bool interactiveFileHeaders ) {
|
||||
if ( UIDiffView::isMultiFileDiff( content ) ) {
|
||||
auto diffViewTitle = i18n( "diff_viewer", "Diff Viewer" ) + ": " + originalFilePath;
|
||||
UIIcon* icon = getUISceneNode()->findIcon( "filetype-diff" );
|
||||
if ( !icon )
|
||||
icon = getUISceneNode()->findIcon( "file" );
|
||||
|
||||
auto scrollView =
|
||||
UIDiffView::NewMultiFileDiffViewer( content, repoPath, mConfig.editor.diffViewMode );
|
||||
auto scrollView = UIDiffView::NewMultiFileDiffViewer(
|
||||
content, repoPath, mConfig.editor.diffViewMode, interactiveFileHeaders );
|
||||
auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle );
|
||||
if ( icon )
|
||||
tab->setIcon( icon->createDrawable( getMenuIconSize() ) );
|
||||
|
||||
+12
-12
@@ -179,11 +179,11 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
|
||||
DrawablePtr findIcon( const std::string& name, const size_t iconSize );
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& getRealDefaultKeybindings();
|
||||
const KeyBindings::ShortcutMap& getRealDefaultKeybindings();
|
||||
|
||||
std::map<KeyBindings::Shortcut, std::string> getDefaultKeybindings();
|
||||
KeyBindings::ShortcutMap getDefaultKeybindings();
|
||||
|
||||
std::map<KeyBindings::Shortcut, std::string> getLocalKeybindings();
|
||||
KeyBindings::ShortcutMap getLocalKeybindings();
|
||||
|
||||
std::map<std::string, std::string> getMigrateKeybindings();
|
||||
|
||||
@@ -581,8 +581,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
void loadDiffFromPaths( const std::string& oldPath, const std::string& newPath );
|
||||
|
||||
void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "",
|
||||
const std::string& oldFilePath = "",
|
||||
const std::string& repoPath = "" );
|
||||
const std::string& oldFilePath = "", const std::string& repoPath = "",
|
||||
bool interactiveFileHeaders = false );
|
||||
|
||||
void loadDiffFromStrings( const std::string& str, const std::string& otherStr );
|
||||
void configureDiffView( UIDiffView* diffView );
|
||||
@@ -619,11 +619,11 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
|
||||
UITabWidget* getSidePanel() const;
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& getRealLocalKeybindings() const;
|
||||
const KeyBindings::ShortcutMap& getRealLocalKeybindings() const;
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& getRealSplitterKeybindings() const;
|
||||
const KeyBindings::ShortcutMap& getRealSplitterKeybindings() const;
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>& getRealTerminalKeybindings() const;
|
||||
const KeyBindings::ShortcutMap& getRealTerminalKeybindings() const;
|
||||
|
||||
const std::string& getFileToOpen() const;
|
||||
|
||||
@@ -699,10 +699,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
std::unordered_map<std::string, std::string> mGlobalSearchKeybindings;
|
||||
std::unordered_map<std::string, std::string> mDocumentSearchKeybindings;
|
||||
std::unordered_map<std::string, std::string> mStatusBarKeybindings;
|
||||
std::map<KeyBindings::Shortcut, std::string> mRealLocalKeybindings;
|
||||
std::map<KeyBindings::Shortcut, std::string> mRealSplitterKeybindings;
|
||||
std::map<KeyBindings::Shortcut, std::string> mRealTerminalKeybindings;
|
||||
std::map<KeyBindings::Shortcut, std::string> mRealDefaultKeybindings;
|
||||
KeyBindings::ShortcutMap mRealLocalKeybindings;
|
||||
KeyBindings::ShortcutMap mRealSplitterKeybindings;
|
||||
KeyBindings::ShortcutMap mRealTerminalKeybindings;
|
||||
KeyBindings::ShortcutMap mRealDefaultKeybindings;
|
||||
std::unordered_map<std::string, std::string> mMousebindings;
|
||||
std::unordered_map<std::string, std::string> mMousebindingsInvert;
|
||||
std::string mConfigPath;
|
||||
|
||||
@@ -72,17 +72,19 @@ void KeybindingsHelper::updateKeybindings(
|
||||
IniFile& ini, const std::string& group, Input* input,
|
||||
std::unordered_map<std::string, std::string>& keybindings,
|
||||
std::unordered_map<std::string, std::string>& invertedKeybindings,
|
||||
const std::map<KeyBindings::Shortcut, std::string>& defKeybindings, bool forceRebind,
|
||||
const KeyBindings::ShortcutMap& defKeybindings, bool forceRebind,
|
||||
const std::map<std::string, std::string>& migrateKeyindings, IniFile& iniState ) {
|
||||
KeyBindings bindings( input );
|
||||
bool added = false;
|
||||
bool migrated = false;
|
||||
const auto orderedDefaultShortcuts = KeyBindings::getOrderedShortcuts( defKeybindings );
|
||||
|
||||
if ( ini.findKey( group ) != IniFile::noID ) {
|
||||
keybindings = ini.getKeyUnorderedMap( group );
|
||||
} else {
|
||||
for ( const auto& it : defKeybindings )
|
||||
ini.setValue( group, bindings.getShortcutString( it.first ), it.second );
|
||||
for ( const auto& shortcut : orderedDefaultShortcuts )
|
||||
ini.setValue( group, bindings.getShortcutString( shortcut ),
|
||||
defKeybindings.find( shortcut )->second );
|
||||
added = true;
|
||||
}
|
||||
for ( const auto& key : keybindings )
|
||||
@@ -93,9 +95,9 @@ void KeybindingsHelper::updateKeybindings(
|
||||
auto foundCmd = invertedKeybindings.find( migrate.first );
|
||||
if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) {
|
||||
KeyBindings::Shortcut shortcut;
|
||||
for ( const auto& defKb : defKeybindings ) {
|
||||
if ( defKb.second == foundCmd->first ) {
|
||||
shortcut = defKb.first;
|
||||
for ( const auto& defaultShortcut : orderedDefaultShortcuts ) {
|
||||
if ( defKeybindings.find( defaultShortcut )->second == foundCmd->first ) {
|
||||
shortcut = defaultShortcut;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -121,15 +123,16 @@ void KeybindingsHelper::updateKeybindings(
|
||||
bool keybindingsWereEmpty = keybindings.empty();
|
||||
|
||||
if ( defKeybindings.size() != keybindings.size() || forceRebind ) {
|
||||
for ( auto& key : defKeybindings ) {
|
||||
auto foundCmd = invertedKeybindings.find( key.second );
|
||||
auto shortcutStr = bindings.getShortcutString( key.first );
|
||||
for ( const auto& shortcut : orderedDefaultShortcuts ) {
|
||||
const auto& command = defKeybindings.find( shortcut )->second;
|
||||
auto foundCmd = invertedKeybindings.find( command );
|
||||
auto shortcutStr = bindings.getShortcutString( shortcut );
|
||||
|
||||
if ( ( foundCmd == invertedKeybindings.end() || keybindingsWereEmpty ) &&
|
||||
keybindings.find( shortcutStr ) == keybindings.end() ) {
|
||||
keybindings[shortcutStr] = key.second;
|
||||
invertedKeybindings[key.second] = shortcutStr;
|
||||
ini.setValue( group, shortcutStr, key.second );
|
||||
keybindings[shortcutStr] = command;
|
||||
invertedKeybindings[command] = shortcutStr;
|
||||
ini.setValue( group, shortcutStr, command );
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class KeybindingsHelper {
|
||||
IniFile& ini, const std::string& group, Input* input,
|
||||
std::unordered_map<std::string, std::string>& keybindings,
|
||||
std::unordered_map<std::string, std::string>& invertedKeybindings,
|
||||
const std::map<KeyBindings::Shortcut, std::string>& defKeybindings, bool forceRebind,
|
||||
const KeyBindings::ShortcutMap& defKeybindings, bool forceRebind,
|
||||
const std::map<std::string, std::string>& migrateKeyindings, IniFile& iniState );
|
||||
|
||||
static void
|
||||
|
||||
@@ -124,7 +124,7 @@ UIWidget* UIBreakpointsTableView::createCell( UIWidget* rowWidget, const ModelIn
|
||||
return UITableView::createCell( rowWidget, index );
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string>
|
||||
const KeyBindings::ShortcutMap
|
||||
StatusDebuggerController::getLocalDefaultKeybindings() {
|
||||
return {
|
||||
{ { KEY_TAB, UICodeEditorSplitter::getDefaultSwitchToTabModifier() }, "next-tab" },
|
||||
|
||||
@@ -42,7 +42,7 @@ class StatusDebuggerController : public StatusBarElement, public UITabWidgetSpli
|
||||
public:
|
||||
enum class State { NotStarted, Running, Paused };
|
||||
|
||||
static const std::map<KeyBindings::Shortcut, std::string> getLocalDefaultKeybindings();
|
||||
static const KeyBindings::ShortcutMap getLocalDefaultKeybindings();
|
||||
|
||||
StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
|
||||
PluginContextProvider* pluginContext );
|
||||
|
||||
@@ -877,6 +877,8 @@ Git::Result Git::diff( DiffMode mode, const std::string& projectDir ) {
|
||||
modeTxt = "--staged";
|
||||
break;
|
||||
}
|
||||
case DiffChanged:
|
||||
break;
|
||||
}
|
||||
return gitSimple( String::format( "diff %s", modeTxt ), projectDir );
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ class Git {
|
||||
bool isEmpty() const { return name.empty(); }
|
||||
};
|
||||
|
||||
enum DiffMode { DiffHead, DiffStaged };
|
||||
enum DiffMode { DiffHead, DiffStaged, DiffChanged };
|
||||
|
||||
Git( const std::string& projectDir = "", const std::string& gitPath = "" );
|
||||
|
||||
|
||||
@@ -212,6 +212,8 @@ GitPlugin::~GitPlugin() {
|
||||
mLifetime.invalidate();
|
||||
waitUntilLoaded();
|
||||
mShuttingDown = true;
|
||||
mCommitDetails.cancelDiffPreparation();
|
||||
mDetachedHistory.details.cancelDiffPreparation();
|
||||
mConflictViewCloseConnection.disconnect();
|
||||
mConflictView = nullptr;
|
||||
mConflictSessions.clear();
|
||||
@@ -2152,10 +2154,13 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
|
||||
case Git::DiffStaged:
|
||||
modeName = "staged";
|
||||
break;
|
||||
case Git::DiffChanged:
|
||||
modeName = "changed";
|
||||
break;
|
||||
}
|
||||
plugin->getPluginContext()->loadDiffFromMemory(
|
||||
res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "",
|
||||
repoPath );
|
||||
res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "", repoPath,
|
||||
true );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
@@ -2992,6 +2997,8 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
|
||||
if ( commit.hash.empty() && !isWorkingTree )
|
||||
return;
|
||||
const std::string selectedRepo = plugin.repoSelected();
|
||||
cancelDiffPreparation();
|
||||
diffPreparationCancelled = std::make_shared<std::atomic_bool>( false );
|
||||
const Uint64 requestGeneration = ++generation;
|
||||
this->commit = commit;
|
||||
workingTree = isWorkingTree;
|
||||
@@ -3163,14 +3170,31 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
|
||||
const std::string repo = this->repo;
|
||||
const Git::Commit commit = this->commit;
|
||||
const bool workingTree = this->workingTree;
|
||||
const auto diffPreparationCancelled = this->diffPreparationCancelled;
|
||||
auto git = plugin.mGit;
|
||||
const auto lifetime = plugin.mLifetime.weakHandle();
|
||||
plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached,
|
||||
workingTree] {
|
||||
workingTree, diffPreparationCancelled] {
|
||||
if ( diffPreparationCancelled &&
|
||||
diffPreparationCancelled->load( std::memory_order_relaxed ) )
|
||||
return;
|
||||
auto result =
|
||||
workingTree ? git->workingTreeFiles( repo ) : git->commitFiles( commit, repo );
|
||||
if ( diffPreparationCancelled &&
|
||||
diffPreparationCancelled->load( std::memory_order_relaxed ) )
|
||||
return;
|
||||
std::shared_ptr<UIDiffView::PreparedMultiFileDiff> preparedDiff;
|
||||
if ( result.success() && !result.patch.empty() ) {
|
||||
preparedDiff =
|
||||
UIDiffView::prepareMultiFileDiff( result.patch, diffPreparationCancelled );
|
||||
if ( diffPreparationCancelled &&
|
||||
diffPreparationCancelled->load( std::memory_order_relaxed ) )
|
||||
return;
|
||||
std::string{}.swap( result.patch );
|
||||
}
|
||||
lifetime.run( [generation, repo, commit, detached, workingTree,
|
||||
result = std::move( result )]( GitPlugin* plugin ) mutable {
|
||||
result = std::move( result ),
|
||||
preparedDiff = std::move( preparedDiff )]( GitPlugin* plugin ) mutable {
|
||||
auto& details = detached ? plugin->mDetachedHistory.details : plugin->mCommitDetails;
|
||||
if ( plugin->mShuttingDown || generation != details.generation ||
|
||||
repo != details.repo || repo != plugin->repoSelected() ||
|
||||
@@ -3230,9 +3254,9 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
|
||||
details.gitHub->setVisible( !details.url.empty() );
|
||||
details.diffContainer->closeAllChildren();
|
||||
details.diff = nullptr;
|
||||
if ( !result.patch.empty() ) {
|
||||
details.diff =
|
||||
UIDiffView::NewMultiFileDiffViewer( result.patch, repo, details.viewMode );
|
||||
if ( preparedDiff ) {
|
||||
details.diff = UIDiffView::NewMultiFileDiffViewer( std::move( preparedDiff ), repo,
|
||||
details.viewMode );
|
||||
details.diff->setLayoutSizePolicy( SizePolicy::MatchParent,
|
||||
SizePolicy::MatchParent );
|
||||
details.diff->setParent( details.diffContainer );
|
||||
@@ -3750,6 +3774,8 @@ void GitPlugin::buildSidePanelTab() {
|
||||
"diff-added" );
|
||||
|
||||
if ( type == Git::GitStatusType::Changed ) {
|
||||
menuAdd( menu, "git-diff-changed",
|
||||
i18n( "git_diff_changed", "Diff Changed" ), "diff-multiple" );
|
||||
menu->addSeparator();
|
||||
menuAdd( menu, "git-discard-all",
|
||||
i18n( "git_discard_all", "Discard All" ) );
|
||||
@@ -3775,6 +3801,8 @@ void GitPlugin::buildSidePanelTab() {
|
||||
(Uint32)Git::GitStatusType::Changed ) );
|
||||
} else if ( id == "git-diff-staged" ) {
|
||||
diff( Git::DiffMode::DiffStaged, repoPath );
|
||||
} else if ( id == "git-diff-changed" ) {
|
||||
diff( Git::DiffMode::DiffChanged, repoPath );
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ class GitPlugin : public PluginBase {
|
||||
Git::Commit commit;
|
||||
std::string repo;
|
||||
std::atomic<Uint64> generation{ 0 };
|
||||
std::shared_ptr<std::atomic_bool> diffPreparationCancelled;
|
||||
EventConnection closeConnection;
|
||||
Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified };
|
||||
bool messageExpanded{ false };
|
||||
@@ -199,7 +200,14 @@ class GitPlugin : public PluginBase {
|
||||
|
||||
void loadCommitFiles( GitPlugin& plugin, bool detached );
|
||||
|
||||
void cancelDiffPreparation() {
|
||||
if ( diffPreparationCancelled )
|
||||
diffPreparationCancelled->store( true, std::memory_order_relaxed );
|
||||
diffPreparationCancelled.reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
cancelDiffPreparation();
|
||||
++generation;
|
||||
view = nullptr;
|
||||
subject = nullptr;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/graphics/drawable.hpp>
|
||||
#include <eepp/ui/doc/textrange.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -170,7 +171,8 @@ class PluginContextProvider {
|
||||
virtual void loadDiffFromMemory( const std::string& content,
|
||||
const std::string& originalFilePath = "",
|
||||
const std::string& oldFilePath = "",
|
||||
const std::string& repoPath = "" ) = 0;
|
||||
const std::string& repoPath = "",
|
||||
bool interactiveFileHeaders = false ) = 0;
|
||||
|
||||
virtual void loadFolder( std::string path, bool forceNewWindow = false ) = 0;
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ void TerminalManager::loadTerminalColorSchemes() {
|
||||
mTerminalCurrentColorScheme = mTerminalColorSchemes.begin()->first;
|
||||
}
|
||||
|
||||
std::map<KeyBindings::Shortcut, std::string> TerminalManager::getTerminalKeybindings() {
|
||||
KeyBindings::ShortcutMap TerminalManager::getTerminalKeybindings() {
|
||||
return {
|
||||
{ { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "create-new-terminal" },
|
||||
{ { KEY_E,
|
||||
|
||||
@@ -34,7 +34,7 @@ class TerminalManager {
|
||||
|
||||
void loadTerminalColorSchemes();
|
||||
|
||||
static std::map<KeyBindings::Shortcut, std::string> getTerminalKeybindings();
|
||||
static KeyBindings::ShortcutMap getTerminalKeybindings();
|
||||
|
||||
const std::string& getTerminalColorSchemesPath() const;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace ecode {
|
||||
|
||||
static const std::map<KeyBindings::Shortcut, std::string> getDefaultKeybindings() {
|
||||
static const KeyBindings::ShortcutMap getDefaultKeybindings() {
|
||||
return {
|
||||
{ { KEY_C, KeyMod::getDefaultModifier() }, "copy" },
|
||||
{ { KEY_X, KeyMod::getDefaultModifier() }, "cut" },
|
||||
|
||||
Reference in New Issue
Block a user