Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions pkg/web_app/lib/src/widget/completion/suggest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:collection/collection.dart';

typedef Suggestions = List<Suggestion>;

class Suggestion {
class Suggestion implements Comparable<Suggestion> {
final int start;
final int end;
final String value;
Expand All @@ -32,15 +32,25 @@ class Suggestion {
'html': html,
'score': score,
};

@override
int compareTo(Suggestion other) {
final sc = -score.compareTo(other.score);
if (sc != 0) return sc;
final lc = value.length.compareTo(other.value.length);
if (lc != 0) return lc;
return value.compareTo(other.value);
}
}

/// Given [data] and [caret] position inside [text] what suggestions do we
/// want to offer and should completion be automatically triggered?
({bool trigger, Suggestions suggestions}) suggest(
({bool trigger, Suggestions suggestions, bool isTrimmed}) suggest(
CompletionData data,
String text,
int caret,
) {
int caret, {
int maxOptionCount = 50,
}) {
// Get position before caret
final beforeCaret = caret > 0 ? caret - 1 : 0;
// Get position of space after the caret
Expand Down Expand Up @@ -81,6 +91,7 @@ class Suggestion {
return (
trigger: false,
suggestions: [],
isTrimmed: false,
);
}

Expand All @@ -94,6 +105,7 @@ class Suggestion {
return (
trigger: false,
suggestions: [],
isTrimmed: false,
);
}
// We don't to auto trigger completion unless there is an option that is
Expand All @@ -106,7 +118,7 @@ class Suggestion {
// Terminate suggestion with a ' ' suffix, if this is a terminal completion
final suffix = completion.terminal ? ' ' : '';

final suggestions = completion.options.map((option) {
var suggestions = completion.options.map((option) {
final overlap = _lcs(prefix, option);
var html = option;
// highlight the overlapping part of the text
Expand All @@ -129,15 +141,32 @@ class Suggestion {
html: html,
score: score,
);
}).sorted((a, b) {
final x = -a.score.compareTo(b.score);
if (x != 0) return x;
return a.value.compareTo(b.value);
});
}).toList();
final isTrimmed = suggestions.length > maxOptionCount;
if (!isTrimmed) {
suggestions.sort();
} else {
// List of score bucket entries ordered by decreasing score.
final buckets = suggestions
.groupListsBy((s) => s.score.floor())
.entries
.toList()
..sort((a, b) => -a.key.compareTo(b.key));
suggestions = [];
for (final bucket in buckets) {
bucket.value.sort();
suggestions
.addAll(bucket.value.take(maxOptionCount - suggestions.length));
if (suggestions.length >= maxOptionCount) {
break;
}
}
}

return (
trigger: trigger,
suggestions: suggestions,
isTrimmed: isTrimmed,
);
}

Expand Down
14 changes: 12 additions & 2 deletions pkg/web_app/lib/src/widget/completion/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ final class _State {
/// Selected suggestion
final int selectedIndex;

/// Whether the suggestion list is trimmed for space consideratoins.
final bool isTrimmed;

_State({
this.inactive = false,
this.closed = false,
Expand All @@ -127,6 +130,7 @@ final class _State {
this.caret = 0,
this.suggestions = const [],
this.selectedIndex = 0,
this.isTrimmed = false,
});

_State update({
Expand All @@ -138,6 +142,7 @@ final class _State {
int? caret,
Suggestions? suggestions,
int? selectedIndex,
bool? isTrimmed,
}) =>
_State(
inactive: inactive ?? this.inactive,
Expand All @@ -148,11 +153,12 @@ final class _State {
caret: caret ?? this.caret,
suggestions: suggestions ?? this.suggestions,
selectedIndex: selectedIndex ?? this.selectedIndex,
isTrimmed: isTrimmed ?? this.isTrimmed,
);

@override
String toString() =>
'_State(forced: $forced, triggered: $triggered, caret: $caret, text: $text, selected: $selectedIndex)';
'_State(forced: $forced, triggered: $triggered, caret: $caret, text: $text, selected: $selectedIndex, isTrimmed: $isTrimmed)';
}

final class _CompletionWidget {
Expand Down Expand Up @@ -212,7 +218,7 @@ final class _CompletionWidget {
delta = state.text.substring(caret, state.caret);
}
final crossedWordBoundary = delta.contains(_whitespace);
final (:trigger, :suggestions) = suggest(
final (:trigger, :suggestions, :isTrimmed) = suggest(
data,
text,
caret,
Expand All @@ -223,6 +229,7 @@ final class _CompletionWidget {
suggestions: suggestions,
text: text,
caret: caret,
isTrimmed: isTrimmed,
);
update();
}
Expand Down Expand Up @@ -262,6 +269,9 @@ final class _CompletionWidget {
..setAttribute('data-completion-option-index', i.toString())
..classList.add(optionClass));
}
if (state.isTrimmed) {
dropdown.appendChild(HTMLDivElement()..textContent = '[...]');
}
}
_renderedSuggestions = state.suggestions;

Expand Down
Loading