mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-04 20:46:29 +03:00
Much better documentation placement in the auto-complete suggestions function/method documentation (SpartanJ/ecode#874).
This commit is contained in:
@@ -171,6 +171,17 @@ void AutoCompletePlugin::load( PluginManager* pluginManager ) {
|
||||
config["max_label_characters"] = mMaxLabelCharacters;
|
||||
updateConfigFile = true;
|
||||
}
|
||||
|
||||
if ( config.contains( "max_suggestion_documentation_width" ) )
|
||||
mMaxSuggestionDocumentationWidth =
|
||||
config.value( "max_suggestion_documentation_width", "100%" );
|
||||
else {
|
||||
config["max_suggestion_documentation_width"] = "100%";
|
||||
updateConfigFile = true;
|
||||
}
|
||||
|
||||
if ( mMaxSuggestionDocumentationWidth.empty() )
|
||||
mMaxSuggestionDocumentationWidth = "100%";
|
||||
}
|
||||
|
||||
if ( mKeyBindings.empty() ) {
|
||||
@@ -1209,33 +1220,33 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc
|
||||
}
|
||||
|
||||
if ( mSuggestionIndex == (int)i && !suggestion.documentation.value.empty() ) {
|
||||
mSuggestionDoc.setFont( editor->getFont() );
|
||||
mSuggestionDoc.setFontSize( editor->getFontSize() );
|
||||
mSuggestionDoc.setFillColor( normalStyle.color );
|
||||
mSuggestionDoc.setStyle( normalStyle.style );
|
||||
bool changed = mSuggestionDoc.setString( suggestion.documentation.value );
|
||||
mSuggestionDoc.setFont( editor->getFont() );
|
||||
mSuggestionDoc.setFontSize( editor->getFontSize() );
|
||||
mSuggestionDoc.setLineWrapMode( LineWrapMode::Word );
|
||||
mSuggestionDoc.setLineWrapKeepIndentation( true );
|
||||
|
||||
Vector2f boxPos = { cursorPos.x + mBoxRect.getWidth(),
|
||||
cursorPos.y + mRowHeight * count };
|
||||
Sizef boxSize = { mSuggestionDoc.getTextWidth() + mBoxPadding.Left + mBoxPadding.Right,
|
||||
mSuggestionDoc.getTextHeight() + mBoxPadding.Top +
|
||||
mBoxPadding.Bottom };
|
||||
primitives.setColor(
|
||||
Color( selectedStyle.background ).blendAlpha( editor->getAlpha() ) );
|
||||
primitives.drawRoundedRectangle( { boxPos, boxSize }, 0.f, Vector2f::One, 6 );
|
||||
Rectf docRect =
|
||||
findBestDocumentationPlacement( editor, suggestion, boxRect,
|
||||
{ { cursorPos.x, cursorPos.y + mRowHeight * count },
|
||||
{ mBoxRect.getWidth(), mRowHeight } },
|
||||
drawUp, lineHeight );
|
||||
|
||||
if ( changed ) {
|
||||
bool forceHTML = String::startsWith( suggestion.detail, "Emmet" );
|
||||
if ( suggestion.documentation.kind == LSPMarkupKind::MarkDown || forceHTML ) {
|
||||
const auto& syntaxDef =
|
||||
forceHTML ? SyntaxDefinitionManager::instance()->getByLSPName( "html" )
|
||||
: SyntaxDefinitionManager::instance()->getByLSPName( "markdown" );
|
||||
SyntaxTokenizer::tokenizeText( syntaxDef, editor->getColorScheme(),
|
||||
&mSuggestionDoc, 0, 0xFFFFFFFF, true, "\n\t " );
|
||||
}
|
||||
if ( docRect.getSize().getWidth() > 0 && docRect.getSize().getHeight() > 0 ) {
|
||||
primitives.setColor(
|
||||
Color( selectedStyle.background ).blendAlpha( editor->getAlpha() ) );
|
||||
|
||||
editor->clipSmartEnable( docRect.Left, docRect.Top, docRect.getWidth(),
|
||||
docRect.getHeight() );
|
||||
|
||||
primitives.drawRoundedRectangle( docRect, 0.f, Vector2f::One, 6 );
|
||||
|
||||
mSuggestionDoc.draw( docRect.Left + mBoxPadding.Left,
|
||||
docRect.Top + mBoxPadding.Top );
|
||||
|
||||
editor->clipSmartDisable();
|
||||
}
|
||||
|
||||
mSuggestionDoc.draw( boxPos.x + mBoxPadding.Left, boxPos.y + mBoxPadding.Top );
|
||||
}
|
||||
count++;
|
||||
visibleStrIndex++;
|
||||
@@ -1259,6 +1270,137 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc
|
||||
(int)eefloor( bar.getWidth() * 0.5f ) );
|
||||
}
|
||||
|
||||
Rectf AutoCompletePlugin::findBestDocumentationPlacement( UICodeEditor* editor,
|
||||
const Suggestion& suggestion,
|
||||
const Rectf& anchorBox,
|
||||
const Rectf& rowRect, bool drawUp,
|
||||
Float lineHeight ) {
|
||||
const Rectf& areaRect = editor->getScreenRect();
|
||||
Float userMaxWidth = editor->convertLength(
|
||||
StyleSheetLength( mMaxSuggestionDocumentationWidth ), editor->getPixelsSize().getWidth() );
|
||||
const Float minSideWidth = PixelDensity::dpToPx( 200.f );
|
||||
const Float margin = PixelDensity::dpToPx( 2.f ); // Visual breathing room
|
||||
|
||||
struct PlacementCandidate {
|
||||
enum Type { Right, Left, Bottom, Top } type;
|
||||
Float availableWidth;
|
||||
Float availableHeight;
|
||||
Float score;
|
||||
};
|
||||
|
||||
// Calculate available space, strictly removing the cursor's line so we don't cover what the
|
||||
// user is typing.
|
||||
Float topAvail = std::max( 0.f, ( drawUp ? ( anchorBox.Top - areaRect.Top - lineHeight )
|
||||
: ( anchorBox.Top - areaRect.Top ) ) -
|
||||
margin );
|
||||
Float bottomAvail =
|
||||
std::max( 0.f, ( drawUp ? ( areaRect.Bottom - anchorBox.Bottom )
|
||||
: ( areaRect.Bottom - anchorBox.Bottom - lineHeight ) ) -
|
||||
margin );
|
||||
Float rightAvail = std::max( 0.f, areaRect.Right - anchorBox.Right - margin );
|
||||
Float leftAvail = std::max( 0.f, anchorBox.Left - areaRect.Left - margin );
|
||||
|
||||
SmallVector<PlacementCandidate, 4> candidates = {
|
||||
{ PlacementCandidate::Right, rightAvail, std::max( 0.f, areaRect.getHeight() - margin * 2 ),
|
||||
0 },
|
||||
{ PlacementCandidate::Left, leftAvail, std::max( 0.f, areaRect.getHeight() - margin * 2 ),
|
||||
0 },
|
||||
{ PlacementCandidate::Bottom, std::max( 0.f, areaRect.getWidth() - margin * 2 ),
|
||||
bottomAvail, 0 },
|
||||
{ PlacementCandidate::Top, std::max( 0.f, areaRect.getWidth() - margin * 2 ), topAvail,
|
||||
0 } };
|
||||
|
||||
for ( auto& c : candidates ) {
|
||||
Float maxW =
|
||||
std::min( userMaxWidth, c.availableWidth - mBoxPadding.Left - mBoxPadding.Right );
|
||||
if ( maxW < 50 || c.availableHeight < mRowHeight * 2 ) {
|
||||
c.score = 0;
|
||||
continue;
|
||||
}
|
||||
c.score = maxW * std::min( c.availableHeight, mRowHeight * 10 );
|
||||
if ( ( c.type == PlacementCandidate::Right || c.type == PlacementCandidate::Left ) &&
|
||||
c.availableWidth >= minSideWidth ) {
|
||||
c.score += 1000000; // Prefer side placement if it has enough width
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( candidates.begin(), candidates.end(),
|
||||
[]( const auto& a, const auto& b ) { return a.score > b.score; } );
|
||||
|
||||
const auto& best = candidates.front();
|
||||
|
||||
// Edge case: No space anywhere on screen (zero score)
|
||||
if ( best.score == 0 ) {
|
||||
return Rectf(); // Return empty rect, `postDraw` will skip drawing
|
||||
}
|
||||
|
||||
Float maxWidth = std::max(
|
||||
0.f, std::min( userMaxWidth, best.availableWidth - mBoxPadding.Left - mBoxPadding.Right ) );
|
||||
|
||||
mSuggestionDoc.setMaxWrapWidth( maxWidth );
|
||||
bool changed = mSuggestionDoc.setString( suggestion.documentation.value );
|
||||
|
||||
if ( changed ) {
|
||||
bool forceHTML = String::startsWith( suggestion.detail, "Emmet" );
|
||||
if ( suggestion.documentation.kind == LSPMarkupKind::MarkDown || forceHTML ) {
|
||||
const auto& syntaxDef =
|
||||
forceHTML ? SyntaxDefinitionManager::instance()->getByLSPName( "html" )
|
||||
: SyntaxDefinitionManager::instance()->getByLSPName( "markdown" );
|
||||
SyntaxTokenizer::tokenizeText( syntaxDef, editor->getColorScheme(), &mSuggestionDoc, 0,
|
||||
0xFFFFFFFF, true, "\n\t " );
|
||||
}
|
||||
}
|
||||
|
||||
Sizef boxSize = { mSuggestionDoc.getTextWidth() + mBoxPadding.Left + mBoxPadding.Right,
|
||||
mSuggestionDoc.getTextHeight() + mBoxPadding.Top + mBoxPadding.Bottom };
|
||||
|
||||
// Height Clamping: Prevent background box from bleeding off-screen.
|
||||
// Text that overflows this will simply get clipped or run over gracefully.
|
||||
boxSize.setHeight( std::min( boxSize.getHeight(), best.availableHeight ) );
|
||||
|
||||
Vector2f pos;
|
||||
if ( best.type == PlacementCandidate::Right ) {
|
||||
pos.x = anchorBox.Right + margin;
|
||||
pos.y = rowRect.Top;
|
||||
} else if ( best.type == PlacementCandidate::Left ) {
|
||||
pos.x = anchorBox.Left - boxSize.getWidth() - margin;
|
||||
pos.y = rowRect.Top;
|
||||
} else if ( best.type == PlacementCandidate::Bottom ) {
|
||||
pos.x = anchorBox.Left;
|
||||
pos.y = anchorBox.Bottom + margin + ( !drawUp ? lineHeight : 0 );
|
||||
} else { // Top
|
||||
pos.x = anchorBox.Left;
|
||||
pos.y = anchorBox.Top - boxSize.getHeight() - margin - ( drawUp ? lineHeight : 0 );
|
||||
}
|
||||
|
||||
// Final Clamping: Kept firmly inside `areaRect` but enforcing boundaries to prevent overlap
|
||||
if ( best.type == PlacementCandidate::Right ) {
|
||||
pos.x = std::min( pos.x, areaRect.Right - boxSize.getWidth() );
|
||||
pos.x = std::max( pos.x, anchorBox.Right + margin );
|
||||
pos.y = std::max( areaRect.Top + margin,
|
||||
std::min( pos.y, areaRect.Bottom - boxSize.getHeight() - margin ) );
|
||||
} else if ( best.type == PlacementCandidate::Left ) {
|
||||
pos.x = std::max( pos.x, areaRect.Left + margin );
|
||||
pos.x = std::min( pos.x, anchorBox.Left - boxSize.getWidth() - margin );
|
||||
pos.y = std::max( areaRect.Top + margin,
|
||||
std::min( pos.y, areaRect.Bottom - boxSize.getHeight() - margin ) );
|
||||
} else if ( best.type == PlacementCandidate::Bottom ) {
|
||||
pos.y = std::min( pos.y, areaRect.Bottom - boxSize.getHeight() - margin );
|
||||
Float minBottom = anchorBox.Bottom + margin + ( !drawUp ? lineHeight : 0 );
|
||||
pos.y = std::max( pos.y, minBottom );
|
||||
pos.x = std::max( areaRect.Left + margin,
|
||||
std::min( pos.x, areaRect.Right - boxSize.getWidth() - margin ) );
|
||||
} else { // Top
|
||||
pos.y = std::max( pos.y, areaRect.Top + margin );
|
||||
Float maxTop = anchorBox.Top - boxSize.getHeight() - margin - ( drawUp ? lineHeight : 0 );
|
||||
pos.y = std::min( pos.y, maxTop );
|
||||
pos.x = std::max( areaRect.Left + margin,
|
||||
std::min( pos.x, areaRect.Right - boxSize.getWidth() - margin ) );
|
||||
}
|
||||
|
||||
return Rectf( pos, boxSize ).round();
|
||||
}
|
||||
|
||||
bool AutoCompletePlugin::onMouseDown( UICodeEditor* editor, const Vector2i& position,
|
||||
const Uint32& flags ) {
|
||||
if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ||
|
||||
|
||||
@@ -45,7 +45,7 @@ class AutoCompletePlugin : public Plugin {
|
||||
sortText( sortText.empty() ? std::string{ this->text } : std::move( sortText ) ),
|
||||
range( range ),
|
||||
insertText( std::move( insertText ) ),
|
||||
documentation( doc ){};
|
||||
documentation( doc ) {};
|
||||
|
||||
bool operator<( const Suggestion& other ) const { return getCmpStr() < other.getCmpStr(); }
|
||||
|
||||
@@ -173,6 +173,7 @@ class AutoCompletePlugin : public Plugin {
|
||||
String::HashType mConfigHash{ 0 };
|
||||
std::unordered_map<std::string, std::string> mKeyBindings;
|
||||
std::unordered_map<std::string, KeyBindings::Shortcut> mShortcuts;
|
||||
std::string mMaxSuggestionDocumentationWidth;
|
||||
|
||||
Float mRowHeight{ 0 };
|
||||
Rectf mBoxRect;
|
||||
@@ -220,6 +221,10 @@ class AutoCompletePlugin : public Plugin {
|
||||
void tryStartSnippetNav( const Suggestion& suggestion, UICodeEditor* editor,
|
||||
const TextRanges& prevSels );
|
||||
|
||||
Rectf findBestDocumentationPlacement( UICodeEditor* editor, const Suggestion& suggestion,
|
||||
const Rectf& anchorBox, const Rectf& rowRect, bool drawUp,
|
||||
Float lineHeight );
|
||||
|
||||
void updateShortcuts();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user