From ef7c0f14571eff5081e13efef5c7564c5b74f744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kon=C3=AD=C4=8Dek?= Date: Mon, 3 May 2010 01:06:57 +0000 Subject: [PATCH] CodeCompletion: filtered items are shown in the same order as they were before filtering. The best match is selected. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5750 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../CodeCompletion/CompletionList.cs | 77 ++++++++++++------- .../CodeCompletion/CompletionListBox.cs | 4 +- .../CodeCompletion/CompletionWindow.cs | 5 +- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs index 2cb41a3930..96ff4ce0d7 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs @@ -28,10 +28,15 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion new FrameworkPropertyMetadata(typeof(CompletionList))); } + bool isFiltering = true; /// - /// If true, enables the old behavior: no filtering, search by string.StartsWith. + /// If true, the CompletionList is filtered to show only matching items. Also enables search by substring. + /// If false, enables the old behavior: no filtering, search by string.StartsWith. /// - public bool IsSearchByStartOnly { get; set; } + public bool IsFiltering { + get { return isFiltering; } + set { isFiltering = value; } + } /// /// Is raised when the completion list indicates that the user has chosen @@ -175,39 +180,49 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion } /// - /// Selects the best match, and possibly filters the items. + /// Selects the best match, and filter the items if turned on using . /// public void SelectItem(string text) { - if (string.IsNullOrEmpty(text)) - return; if (listBox == null) ApplyTemplate(); - if (this.IsSearchByStartOnly) - SelectItemWithStart(text); - else + if (this.IsFiltering) { FilterMatchingItems(text); + } + else { + SelectItemWithStart(text); + } } + /// + /// Filters CompletionList items to show only those matching given text, and selects the best match. + /// void FilterMatchingItems(string text) { - // BUG Find references to itemsWithQualities returns just this one - // assign qualities to items - var itemsWithQualities = - from item in completionData + var itemsWithQualities = + from item in this.completionData select new { Item = item, Quality = GetMatchQuality(item.Text, text) }; - // take items with quality > 0, order by quality - var matchingOrdered = from itemWithQ in itemsWithQualities - where itemWithQ.Quality > 0 - orderby itemWithQ.Quality descending, itemWithQ.Item.Priority descending, itemWithQ.Item.Text - select itemWithQ.Item; - var matchingItems = new ObservableCollection(); - foreach (var matchingItem in matchingOrdered) { - matchingItems.Add(matchingItem); + var matchingItems = itemsWithQualities.Where(item => item.Quality > 0); + + var listBoxItems = new ObservableCollection(); + int bestIndex = -1; + int bestQuality = -1; + double bestPriority = 0; + int i = 0; + foreach (var matchingItem in matchingItems) { + double priority = matchingItem.Item.Priority; + int quality = matchingItem.Quality; + if (quality > bestQuality || (quality == bestQuality && priority > bestPriority)) { + bestIndex = i; + bestPriority = priority; + bestQuality = quality; + } + listBoxItems.Add(matchingItem.Item); + i++; } - listBox.ItemsSource = matchingItems; - listBox.SelectIndex(0); + listBox.ItemsSource = listBoxItems; + SelectIndexCentered(bestIndex); } /// @@ -215,6 +230,9 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion /// void SelectItemWithStart(string startText) { + if (string.IsNullOrEmpty(startText)) + return; + int selectedItem = listBox.SelectedIndex; int bestIndex = -1; @@ -231,6 +249,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion useThisItem = true; } else { if (bestIndex == selectedItem) { + // martin.konicek: I'm not sure what this does. This item could have higher priority, why not select it? useThisItem = false; } else if (i == selectedItem) { useThisItem = bestQuality == quality; @@ -244,11 +263,17 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion bestQuality = quality; } } + SelectIndexCentered(bestIndex); + } + + void SelectIndexCentered(int bestIndex) + { if (bestIndex < 0) { listBox.ClearSelection(); } else { int firstItem = listBox.FirstVisibleItem; if (bestIndex < firstItem || firstItem + listBox.VisibleItemCount <= bestIndex) { + // CenterViewOn does nothing as CompletionListBox.ScrollViewer is null listBox.CenterViewOn(bestIndex); listBox.SelectIndex(bestIndex); } else { @@ -264,7 +289,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion // 1 = match CamelCase // 2 = match sustring // 3 = match substring case sensitive - // 4 = match CamelCase when length of query is only 1 or 2 characters + // 4 = match CamelCase when length of query is 1 or 2 characters // 5 = match start // 6 = match start case sensitive // 7 = full match @@ -282,10 +307,10 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion if (query.Length <= 2 && CamelCaseMatch(itemText, query)) return 4; - // search by substring, if not turned off - if (!IsSearchByStartOnly && itemText.Contains(query)) + // search by substring, if filtering (i.e. new behavior) turned on + if (IsFiltering && query.Length > 1 && itemText.Contains(query)) return 3; - if (!IsSearchByStartOnly && itemText.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) + if (IsFiltering && query.Length > 1 && itemText.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) return 2; if (CamelCaseMatch(itemText, query)) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs index 96a7741532..cf4406d61d 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion /// public int FirstVisibleItem { get { - if (scrollViewer == null) { + if (scrollViewer == null || scrollViewer.ExtentHeight == 0) { return 0; } else { return (int)(this.Items.Count * scrollViewer.VerticalOffset / scrollViewer.ExtentHeight); @@ -57,7 +57,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion /// public int VisibleItemCount { get { - if (scrollViewer == null) { + if (scrollViewer == null || scrollViewer.ExtentHeight == 0) { return 10; } else { return Math.Max( diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs index 7a3fe83c50..c3f6d3528f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs @@ -163,8 +163,11 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion { int offset = this.TextArea.Caret.Offset; if (offset == this.StartOffset) { - if (CloseAutomatically && CloseWhenCaretAtBeginning) + if (CloseAutomatically && CloseWhenCaretAtBeginning) { Close(); + } else { + completionList.SelectItem(string.Empty); + } return; } if (offset < this.StartOffset || offset > this.EndOffset) {