|
11 | 11 | using Avalonia.Media; |
12 | 12 |
|
13 | 13 | using AvaloniaEdit; |
| 14 | +using AvaloniaEdit.CodeCompletion; |
14 | 15 | using AvaloniaEdit.Document; |
15 | 16 | using AvaloniaEdit.Editing; |
16 | 17 | using AvaloniaEdit.Rendering; |
| 18 | +using AvaloniaEdit.Utils; |
17 | 19 |
|
18 | 20 | namespace SourceGit.Views |
19 | 21 | { |
| 22 | + public class CommitMessageCodeCompletionData : ICompletionData |
| 23 | + { |
| 24 | + public IImage Image |
| 25 | + { |
| 26 | + get => null; |
| 27 | + } |
| 28 | + |
| 29 | + public string Text |
| 30 | + { |
| 31 | + get; |
| 32 | + } |
| 33 | + |
| 34 | + public object Content |
| 35 | + { |
| 36 | + get => Text; |
| 37 | + } |
| 38 | + |
| 39 | + public object Description |
| 40 | + { |
| 41 | + get => null; |
| 42 | + } |
| 43 | + |
| 44 | + public double Priority |
| 45 | + { |
| 46 | + get => 0; |
| 47 | + } |
| 48 | + |
| 49 | + public CommitMessageCodeCompletionData(string text) |
| 50 | + { |
| 51 | + Text = text; |
| 52 | + } |
| 53 | + |
| 54 | + public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs) |
| 55 | + { |
| 56 | + textArea.Document.Replace(completionSegment, Text); |
| 57 | + } |
| 58 | + } |
| 59 | + |
20 | 60 | public class CommitMessageTextEditor : TextEditor |
21 | 61 | { |
22 | 62 | public static readonly StyledProperty<string> CommitMessageProperty = |
@@ -183,9 +223,50 @@ protected override void OnTextChanged(EventArgs e) |
183 | 223 | { |
184 | 224 | base.OnTextChanged(e); |
185 | 225 |
|
| 226 | + if (!IsLoaded) |
| 227 | + return; |
| 228 | + |
186 | 229 | _isEditing = true; |
187 | 230 | SetCurrentValue(CommitMessageProperty, Text); |
188 | 231 | _isEditing = false; |
| 232 | + |
| 233 | + var caretOffset = CaretOffset; |
| 234 | + var start = caretOffset; |
| 235 | + while (start > 0 && !char.IsWhiteSpace(Text[start - 1])) |
| 236 | + start--; |
| 237 | + |
| 238 | + if (caretOffset == start) |
| 239 | + { |
| 240 | + _completionWnd?.Close(); |
| 241 | + return; |
| 242 | + } |
| 243 | + |
| 244 | + var word = Text.Substring(start, caretOffset - start); |
| 245 | + var matches = new List<CommitMessageCodeCompletionData>(); |
| 246 | + foreach (var keyword in _keywords) |
| 247 | + { |
| 248 | + if (keyword.StartsWith(word, StringComparison.OrdinalIgnoreCase) && keyword.Length != word.Length) |
| 249 | + matches.Add(new(keyword)); |
| 250 | + } |
| 251 | + |
| 252 | + if (matches.Count > 0) |
| 253 | + { |
| 254 | + if (_completionWnd == null) |
| 255 | + { |
| 256 | + _completionWnd = new CompletionWindow(TextArea); |
| 257 | + _completionWnd.Closed += (_, ev) => _completionWnd = null; |
| 258 | + _completionWnd.Show(); |
| 259 | + } |
| 260 | + |
| 261 | + _completionWnd.CompletionList.CompletionData.Clear(); |
| 262 | + _completionWnd.CompletionList.CompletionData.AddRange(matches); |
| 263 | + _completionWnd.StartOffset = start; |
| 264 | + _completionWnd.EndOffset = caretOffset; |
| 265 | + } |
| 266 | + else |
| 267 | + { |
| 268 | + _completionWnd?.Close(); |
| 269 | + } |
189 | 270 | } |
190 | 271 |
|
191 | 272 | private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) |
@@ -235,7 +316,9 @@ private void OnTextViewVisualLinesChanged(object sender, EventArgs e) |
235 | 316 | InvalidateVisual(); |
236 | 317 | } |
237 | 318 |
|
| 319 | + private readonly List<string> _keywords = ["git", "GitHub", "GitLab", "Acked-by: ", "Co-authored-by: ", "Reviewed-by: ", "Signed-off-by: ", "BREAKING CHANGE: "]; |
238 | 320 | private bool _isEditing = false; |
| 321 | + private CompletionWindow _completionWnd = null; |
239 | 322 | } |
240 | 323 |
|
241 | 324 | public partial class CommitMessageToolBox : UserControl |
|
0 commit comments