diff --git a/External/Plugins/ASCompletion/Completion/ASComplete.cs b/External/Plugins/ASCompletion/Completion/ASComplete.cs index 07d13bb271..c1f092282f 100644 --- a/External/Plugins/ASCompletion/Completion/ASComplete.cs +++ b/External/Plugins/ASCompletion/Completion/ASComplete.cs @@ -242,7 +242,7 @@ static public bool OnShortcut(Keys keys, ScintillaControl Sci) // dot complete if (keys == (Keys.Control | Keys.Space)) { - if (ASContext.HasContext && ASContext.Context.IsFileValid) + if (ASContext.HasContext && ASContext.Context.IsFileValid && Sci.ContainsFocus) { // try to get completion as if we had just typed the previous char if (OnChar(Sci, Sci.CharAt(Sci.PositionBefore(Sci.CurrentPos)), false)) @@ -258,7 +258,7 @@ static public bool OnShortcut(Keys keys, ScintillaControl Sci) } else if (keys == Keys.Back) { - HandleAddClosingBraces(Sci, Sci.CurrentChar, false); + if (Sci.ContainsFocus) HandleAddClosingBraces(Sci, Sci.CurrentChar, false); return false; } // show calltip @@ -276,7 +276,8 @@ static public bool OnShortcut(Keys keys, ScintillaControl Sci) // project types completion else if (keys == (Keys.Control | Keys.Alt | Keys.Space)) { - if (ASContext.HasContext && ASContext.Context.IsFileValid && !ASContext.Context.Settings.LazyClasspathExploration) + if (ASContext.HasContext && ASContext.Context.IsFileValid && + !ASContext.Context.Settings.LazyClasspathExploration && Sci.ContainsFocus) { int position = Sci.CurrentPos-1; string tail = GetWordLeft(Sci, ref position); @@ -1332,7 +1333,7 @@ static private void ShowCalltip(ScintillaControl Sci, int paramIndex, bool force prevParam = paramName; calltipDetails = UITools.Manager.ShowDetails; string text = calltipDef + ASDocumentation.GetTipDetails(calltipMember, paramName); - UITools.CallTip.CallTipShow(Sci, calltipPos - calltipOffset, text, forceRedraw); + UITools.CallTip.CallTipShow(calltipPos - calltipOffset, text, forceRedraw); } // highlight @@ -4610,4 +4611,4 @@ public sealed class ResolvedContext public ClassModel TokenType; } #endregion -} \ No newline at end of file +} diff --git a/External/Plugins/ASCompletion/PluginMain.cs b/External/Plugins/ASCompletion/PluginMain.cs index 117bb111ee..b61bce3e97 100644 --- a/External/Plugins/ASCompletion/PluginMain.cs +++ b/External/Plugins/ASCompletion/PluginMain.cs @@ -921,22 +921,21 @@ private void OnTextChanged(ScintillaControl sender, int position, int length, in ASContext.OnTextChanged(sender, position, length, linesAdded); } - private void OnUpdateCallTip(ScintillaControl sci, int position) + private void OnUpdateCallTip(Control control, int position) { - if (ASComplete.HasCalltip()) - { - int pos = sci.CurrentPos - 1; - char c = (char)sci.CharAt(pos); - if ((c == ',' || c == '(') && sci.BaseStyleAt(pos) == 0) - sci.Colourise(0, -1); - ASComplete.HandleFunctionCompletion(sci, false, true); - } + var sci = (ScintillaControl) control; + int pos = sci.CurrentPos - 1; + char c = (char)sci.CharAt(pos); + if ((c == ',' || c == '(') && sci.BaseStyleAt(pos) == 0) + sci.Colourise(0, -1); + if (!ASComplete.HandleFunctionCompletion(sci, false, true)) + UITools.CallTip.Hide(); } - private void OnUpdateSimpleTip(ScintillaControl sci, Point mousePosition) + private void OnUpdateSimpleTip(Control sci, Point mousePosition) { if (UITools.Tip.Visible) - OnMouseHover(sci, lastHoverPosition); + OnMouseHover((ScintillaNet.ScintillaControl)sci, lastHoverPosition); } void timerPosition_Elapsed(object sender, ElapsedEventArgs e) diff --git a/External/Plugins/BasicCompletion/PluginMain.cs b/External/Plugins/BasicCompletion/PluginMain.cs index bfb5343b98..b52235cc68 100644 --- a/External/Plugins/BasicCompletion/PluginMain.cs +++ b/External/Plugins/BasicCompletion/PluginMain.cs @@ -132,13 +132,15 @@ public void HandleEvent(Object sender, NotifyEvent e, HandlingPriority priority) Keys keys = (e as KeyEvent).Value; if (this.isSupported && keys == (Keys.Control | Keys.Space)) { - String lang = document.SciControl.ConfigurationLanguage; + var sci = document.SciControl; + if (!sci.ContainsFocus) return; + String lang = sci.ConfigurationLanguage; List items = this.GetCompletionListItems(lang, document.FileName); if (items != null && items.Count > 0) { items.Sort(); - Int32 curPos = document.SciControl.CurrentPos - 1; - String curWord = document.SciControl.GetWordLeft(curPos, false); + Int32 curPos = sci.CurrentPos - 1; + String curWord = sci.GetWordLeft(curPos, false); if (curWord == null) curWord = String.Empty; CompletionList.Show(items, false, curWord); e.Handled = true; diff --git a/External/Plugins/CssCompletion/PluginMain.cs b/External/Plugins/CssCompletion/PluginMain.cs index a85412244f..d79bb506fa 100644 --- a/External/Plugins/CssCompletion/PluginMain.cs +++ b/External/Plugins/CssCompletion/PluginMain.cs @@ -126,11 +126,12 @@ public void HandleEvent(Object sender, NotifyEvent e, HandlingPriority priority) case EventType.Keys: { Keys keys = (e as KeyEvent).Value; - if (this.IsSupported(document) && keys == (Keys.Control | Keys.Space)) + if (this.IsSupported(document) && keys == (Keys.Control | Keys.Space) && completion != null) { - if (completion != null) + var sci = document.SciControl; + if (sci.ContainsFocus) { - completion.OnComplete(document.SciControl, document.SciControl.CurrentPos); + completion.OnComplete(sci, sci.CurrentPos); e.Handled = true; } } diff --git a/External/Plugins/XMLCompletion/XMLComplete.cs b/External/Plugins/XMLCompletion/XMLComplete.cs index 030bc82301..5e3145f1fe 100644 --- a/External/Plugins/XMLCompletion/XMLComplete.cs +++ b/External/Plugins/XMLCompletion/XMLComplete.cs @@ -650,6 +650,7 @@ public static Boolean OnShortCut(Keys keys) ITabbedDocument document = PluginBase.MainForm.CurrentDocument; if (!document.IsEditable) return false; ScintillaControl sci = document.SciControl; + if (!sci.ContainsFocus) return false; XMLContextTag ctag = GetXMLContextTag(sci, sci.CurrentPos); // Starting tag if (ctag.Tag == null) diff --git a/FlashDevelop/Docking/TabbedDocument.cs b/FlashDevelop/Docking/TabbedDocument.cs index 20c9f82d09..ce26995bdd 100644 --- a/FlashDevelop/Docking/TabbedDocument.cs +++ b/FlashDevelop/Docking/TabbedDocument.cs @@ -312,8 +312,8 @@ public void AddEditorControls(String file, String text, Int32 codepage) this.editor.MarkerDeleteAll(2); this.IsModified = false; }; - this.editor.FocusChanged += new FocusHandler(this.EditorFocusChanged); - this.editor2.FocusChanged += new FocusHandler(this.EditorFocusChanged); + this.editor.GotFocus += EditorFocusChanged; + this.editor2.GotFocus += EditorFocusChanged; this.editor.UpdateSync += new UpdateSyncHandler(this.EditorUpdateSync); this.editor2.UpdateSync += new UpdateSyncHandler(this.EditorUpdateSync); this.Controls.Add(this.splitContainer); @@ -342,14 +342,11 @@ private void EditorUpdateSync(ScintillaControl sender) /// /// When the user changes to sci, block events from inactive sci /// - private void EditorFocusChanged(ScintillaControl sender) + private void EditorFocusChanged(object sender, EventArgs e) { - if (sender.IsFocus) - { - this.lastEditor = sender; - this.editor.DisableAllSciEvents = (sender == editor2); - this.editor2.DisableAllSciEvents = (sender == editor); - } + this.lastEditor = (ScintillaControl)sender; + this.editor.DisableAllSciEvents = (sender == editor2); + this.editor2.DisableAllSciEvents = (sender == editor); } /// diff --git a/FlashDevelop/MainForm.cs b/FlashDevelop/MainForm.cs index 7dd71dfb8a..06b323636c 100644 --- a/FlashDevelop/MainForm.cs +++ b/FlashDevelop/MainForm.cs @@ -1580,13 +1580,19 @@ public Boolean PreFilterMessage(ref Message m) ITabbedDocument doc = Globals.CurrentDocument; if (Control.FromHandle(hWnd) != null) { - Win32.SendMessage(hWnd, m.Msg, m.WParam, m.LParam); - return true; - } - else if (doc != null && doc.IsEditable && (hWnd == doc.SplitSci1.HandleSci || hWnd == doc.SplitSci2.HandleSci)) - { - Win32.SendMessage(hWnd, m.Msg, m.WParam, m.LParam); - return true; + if (hWnd == doc.SplitSci1.Handle || hWnd == doc.SplitSci2.Handle) + { + if (doc != null && doc.IsEditable) + { + Win32.SendMessage(hWnd, m.Msg, m.WParam, m.LParam); + return true; + } + } + else + { + Win32.SendMessage(hWnd, m.Msg, m.WParam, m.LParam); + return true; + } } } } diff --git a/PluginCore/PluginCore.csproj b/PluginCore/PluginCore.csproj index 80630e4e9b..e5a71d5443 100644 --- a/PluginCore/PluginCore.csproj +++ b/PluginCore/PluginCore.csproj @@ -312,6 +312,11 @@ Component + + + + Form + Component diff --git a/PluginCore/PluginCore/Controls/CompletionList.cs b/PluginCore/PluginCore/Controls/CompletionList.cs index 1c19c9ed96..d4a0eea206 100644 --- a/PluginCore/PluginCore/Controls/CompletionList.cs +++ b/PluginCore/PluginCore/Controls/CompletionList.cs @@ -1,17 +1,17 @@ +// NOTE: We may well dump this static class, or mark it as deprecated, and create UITools.CompletionList, it would make the code look bit more organized and in line with other code in there + using System; using System.Drawing; using System.Collections.Generic; -using System.Text.RegularExpressions; using System.Windows.Forms; -using PluginCore.Managers; -using PluginCore.Helpers; using ScintillaNet; namespace PluginCore.Controls { + public delegate void InsertedTextHandler(ScintillaControl sender, int position, string text, char trigger, ICompletionListItem item); - public class CompletionList + public static class CompletionList { static public event InsertedTextHandler OnInsert; static public event InsertedTextHandler OnCancel; @@ -19,37 +19,25 @@ public class CompletionList /// /// Properties of the class /// - private static System.Timers.Timer tempo; - private static System.Timers.Timer tempoTip; - private static System.Windows.Forms.ListBox completionList; + internal static CompletionListControl completionList; #region State Properties - private static bool disableSmartMatch; - private static ICompletionListItem currentItem; - private static List allItems; - private static Boolean exactMatchInList; - private static Boolean smartMatchInList; - private static Boolean autoHideList; - private static Boolean noAutoInsert; - private static Boolean isActive; - internal static Boolean listUp; - private static Boolean fullList; - private static Int32 startPos; - private static Int32 currentPos; - private static Int32 lastIndex; - private static String currentWord; - private static String word; - private static Boolean needResize; - private static String widestLabel; - private static long showTime; - private static ICompletionListItem defaultItem; + internal static Boolean listUp + { + get { return completionList.listUp; } + set { completionList.listUp = value; } + } /// /// Set to 0 after calling .Show to keep the completion list active /// when the text was erased completely (using backspace) /// - public static Int32 MinWordLength; + public static Int32 MinWordLength + { + get { return completionList.MinWordLength; } + set { completionList.MinWordLength = value; } + } #endregion @@ -60,29 +48,11 @@ public class CompletionList /// public static void CreateControl(IMainForm mainForm) { - tempo = new System.Timers.Timer(); - tempo.SynchronizingObject = (Form)mainForm; - tempo.Elapsed += new System.Timers.ElapsedEventHandler(DisplayList); - tempo.AutoReset = false; - tempoTip = new System.Timers.Timer(); - tempoTip.SynchronizingObject = (Form)mainForm; - tempoTip.Elapsed += new System.Timers.ElapsedEventHandler(UpdateTip); - tempoTip.AutoReset = false; - tempoTip.Interval = 800; - - completionList = new ListBox(); - completionList.Font = new System.Drawing.Font(PluginBase.Settings.DefaultFont, FontStyle.Regular); - completionList.Visible = false; - completionList.Location = new Point(400,200); - completionList.ItemHeight = completionList.Font.Height + 2; - completionList.Size = new Size(180, 100); - completionList.DrawMode = DrawMode.OwnerDrawFixed; - completionList.DrawItem += new DrawItemEventHandler(CLDrawListItem); - completionList.Click += new EventHandler(CLClick); - completionList.DoubleClick += new EventHandler(CLDoubleClick); - mainForm.Controls.Add(completionList); + completionList = new CompletionListControl(new ScintillaHost()); + completionList.OnCancel += OnCancelHandler; + completionList.OnInsert += OnInsertHandler; } - + #endregion #region Public List Properties @@ -92,7 +62,7 @@ public static void CreateControl(IMainForm mainForm) /// public static Boolean Active { - get { return isActive; } + get { return completionList.Active; } } /// @@ -102,8 +72,7 @@ public static Boolean HasMouseIn { get { - if (!isActive || completionList == null) return false; - return completionList.ClientRectangle.Contains(completionList.PointToClient(Control.MousePosition)); + return completionList.HasMouseIn; } } @@ -114,9 +83,7 @@ public static string SelectedLabel { get { - if (completionList == null) return null; - ICompletionListItem selected = completionList.SelectedItem as ICompletionListItem; - return (selected == null) ? null : selected.Label; + return completionList.SelectedLabel; } } @@ -129,7 +96,7 @@ public static string SelectedLabel /// public static Boolean CheckPosition(Int32 position) { - return position == currentPos; + return completionList.CheckPosition(position); } /// @@ -137,17 +104,7 @@ public static Boolean CheckPosition(Int32 position) /// static public void Show(List itemList, Boolean autoHide, String select) { - if (!string.IsNullOrEmpty(select)) - { - int maxLen = 0; - foreach (ICompletionListItem item in itemList) - if (item.Label.Length > maxLen) maxLen = item.Label.Length; - maxLen = Math.Min(256, maxLen); - if (select.Length > maxLen) select = select.Substring(0, maxLen); - currentWord = select; - } - else currentWord = null; - Show(itemList, autoHide); + completionList.Show(itemList, autoHide, select); } /// @@ -155,58 +112,7 @@ static public void Show(List itemList, Boolean autoHide, St /// static public void Show(List itemList, bool autoHide) { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) return; - ScintillaControl sci = doc.SciControl; - try - { - if ((itemList == null) || (itemList.Count == 0)) - { - if (isActive) Hide(); - return; - } - if (sci == null) - { - if (isActive) Hide(); - return; - } - } - catch (Exception ex) - { - ErrorManager.ShowError(ex); - } - // state - allItems = itemList; - autoHideList = autoHide; - noAutoInsert = false; - word = ""; - if (currentWord != null) - { - word = currentWord; - currentWord = null; - } - MinWordLength = 1; - fullList = (word.Length == 0) || !autoHide || !PluginBase.MainForm.Settings.AutoFilterList; - lastIndex = 0; - exactMatchInList = false; - if (sci.SelectionStart == sci.SelectionEnd) - startPos = sci.CurrentPos - word.Length; - else - startPos = sci.SelectionStart; - currentPos = sci.SelectionEnd; // sci.CurrentPos; - defaultItem = null; - // populate list - needResize = true; - tempo.Enabled = autoHide && (PluginBase.MainForm.Settings.DisplayDelay > 0); - if (tempo.Enabled) tempo.Interval = PluginBase.MainForm.Settings.DisplayDelay; - FindWordStartingWith(word); - // state - isActive = true; - tempoTip.Enabled = false; - showTime = DateTime.Now.Ticks; - disableSmartMatch = noAutoInsert || PluginBase.MainForm.Settings.DisableSmartMatch; - UITools.Manager.LockControl(sci); - faded = false; + completionList.Show(itemList, autoHide); } /// @@ -214,102 +120,28 @@ static public void Show(List itemList, bool autoHide) /// static public void SelectItem(String name) { - int p = name.IndexOf('<'); - if (p > 1) name = name.Substring(0, p) + ""; - string pname = (name.IndexOf('.') < 0) ? "." + name : null; - ICompletionListItem found = null; - foreach (ICompletionListItem item in completionList.Items) - { - if (item.Label == name) - { - defaultItem = item; - completionList.SelectedItem = item; - return; - } - if (pname != null && item.Label.EndsWithOrdinal(pname)) found = item; - } - if (found != null) - { - defaultItem = found; - completionList.SelectedItem = found; - } + completionList.SelectItem(name); } /// - /// Require that completion items are explicitly inserted (Enter, Tab, mouse-click) + /// Require that completion items are explicitely inserted (Enter, Tab, mouse-click) /// public static void DisableAutoInsertion() { - noAutoInsert = true; - } - - /// - /// - /// - static private void DisplayList(Object sender, System.Timers.ElapsedEventArgs e) - { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) return; - ScintillaControl sci = doc.SciControl; - ListBox cl = completionList; - if (cl.Items.Count == 0) return; - - // measure control - if (needResize && !string.IsNullOrEmpty(widestLabel)) - { - needResize = false; - Graphics g = cl.CreateGraphics(); - SizeF size = g.MeasureString(widestLabel, cl.Font); - cl.Width = (int)Math.Min(Math.Max(size.Width + 40, 100), 400) + ScaleHelper.Scale(10); - } - int newHeight = Math.Min(cl.Items.Count, 10) * cl.ItemHeight + 4; - if (newHeight != cl.Height) cl.Height = newHeight; - // place control - Point coord = new Point(sci.PointXFromPosition(startPos), sci.PointYFromPosition(startPos)); - listUp = UITools.CallTip.CallTipActive || (coord.Y+cl.Height > sci.Height); - coord = sci.PointToScreen(coord); - coord = ((Form)PluginBase.MainForm).PointToClient(coord); - cl.Left = coord.X-20 + sci.Left; - if (listUp) cl.Top = coord.Y-cl.Height; - else cl.Top = coord.Y + UITools.Manager.LineHeight(sci); - // Keep on control area - if (cl.Right > ((Form)PluginBase.MainForm).ClientRectangle.Right) - { - cl.Left = ((Form)PluginBase.MainForm).ClientRectangle.Right - cl.Width; - } - if (!cl.Visible) - { - Redraw(); - cl.Show(); - cl.BringToFront(); - if (UITools.CallTip.CallTipActive) UITools.CallTip.PositionControl(sci); - } + completionList.DisableAutoInsertion(); } static public void Redraw() { - Color back = PluginBase.MainForm.GetThemeColor("CompletionList.BackColor"); - completionList.BackColor = back == Color.Empty ? System.Drawing.SystemColors.Window : back; + completionList.Redraw(); } /// /// Hide completion list - /// + /// static public void Hide() { - if (completionList != null && isActive) - { - tempo.Enabled = false; - isActive = false; - fullList = false; - faded = false; - completionList.Visible = false; - if (completionList.Items.Count > 0) completionList.Items.Clear(); - currentItem = null; - allItems = null; - UITools.Tip.Hide(); - if (!UITools.CallTip.CallTipActive) UITools.Manager.UnlockControl(); - } + completionList.Hide(); } /// @@ -317,16 +149,7 @@ static public void Hide() /// static public void Hide(char trigger) { - if (completionList != null && isActive) - { - Hide(); - if (OnCancel != null) - { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) return; - OnCancel(doc.SciControl, currentPos, currentWord, trigger, null); - } - } + completionList.Hide(trigger); } /// @@ -334,55 +157,7 @@ static public void Hide(char trigger) /// static public void SelectWordInList(String tail) { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) - { - Hide(); - return; - } - ScintillaControl sci = doc.SciControl; - currentWord = tail; - currentPos += tail.Length; - sci.SetSel(currentPos, currentPos); - } - - /// - /// - /// - static private void CLDrawListItem(Object sender, System.Windows.Forms.DrawItemEventArgs e) - { - ICompletionListItem item = completionList.Items[e.Index] as ICompletionListItem; - e.DrawBackground(); - Color fore = PluginBase.MainForm.GetThemeColor("CompletionList.ForeColor", SystemColors.WindowText); - Color sel = PluginBase.MainForm.GetThemeColor("CompletionList.SelectedTextColor", SystemColors.HighlightText); - bool selected = (e.State & DrawItemState.Selected) > 0; - Brush textBrush = (selected) ? new SolidBrush(sel) : new SolidBrush(fore); - Brush packageBrush = new SolidBrush(PluginBase.MainForm.GetThemeColor("CompletionList.PackageColor", Color.Gray)); - Rectangle tbounds = new Rectangle(ScaleHelper.Scale(18), e.Bounds.Top, e.Bounds.Width, e.Bounds.Height); - if (item != null) - { - Graphics g = e.Graphics; - float newHeight = e.Bounds.Height - 2; - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.DrawImage(item.Icon, 1, e.Bounds.Top + ((e.Bounds.Height - newHeight) / 2), newHeight, newHeight); - int p = item.Label.LastIndexOf('.'); - if (p > 0 && !selected) - { - string package = item.Label.Substring(0, p + 1); - g.DrawString(package, e.Font, packageBrush, tbounds, StringFormat.GenericDefault); - int left = tbounds.Left + DrawHelper.MeasureDisplayStringWidth(e.Graphics, package, e.Font) - 2; - if (left < tbounds.Right) g.DrawString(item.Label.Substring(p + 1), e.Font, textBrush, left, tbounds.Top, StringFormat.GenericDefault); - } - else g.DrawString(item.Label, e.Font, textBrush, tbounds, StringFormat.GenericDefault); - } - e.DrawFocusRectangle(); - if ((item != null) && ((e.State & DrawItemState.Selected) > 0)) - { - UITools.Tip.Hide(); - currentItem = item; - tempoTip.Stop(); - tempoTip.Start(); - } + completionList.SelectWordInList(tail); } /// @@ -390,685 +165,294 @@ static private void CLDrawListItem(Object sender, System.Windows.Forms.DrawItemE /// static public void UpdateTip(Object sender, System.Timers.ElapsedEventArgs e) { - tempoTip.Stop(); - if (currentItem == null || faded) - return; - - UITools.Tip.SetText(currentItem.Description ?? "", false); - UITools.Tip.Redraw(false); - - int rightWidth = ((Form)PluginBase.MainForm).ClientRectangle.Right - completionList.Right - 10; - int leftWidth = completionList.Left; - - Point posTarget = new Point(completionList.Right, completionList.Top); - int widthTarget = rightWidth; - if (rightWidth < 220 && leftWidth > 220) - { - widthTarget = leftWidth; - posTarget = new Point(0, completionList.Top); - } - - UITools.Tip.Location = posTarget; - UITools.Tip.AutoSize(widthTarget, 500); + completionList.UpdateTip(sender, e); + } - if (widthTarget == leftWidth) - UITools.Tip.Location = new Point(completionList.Left - UITools.Tip.Size.Width, posTarget.Y); + static public int SmartMatch(string label, string word, int len) + { + return completionList.SmartMatch(label, word, len); + } - UITools.Tip.Show(); + /// + /// Filter the completion list with the letter typed + /// + static public void FindWordStartingWith(String word) + { + completionList.FindWordStartingWith(word); } /// /// - /// - static private void CLClick(Object sender, System.EventArgs e) + /// + static public bool ReplaceText(ScintillaControl sci, char trigger) { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) - { - Hide(); - return; - } - doc.SciControl.Focus(); + return completionList.ReplaceText("", trigger); } /// /// /// - static private void CLDoubleClick(Object sender, System.EventArgs e) + static public bool ReplaceText(ScintillaControl sci, String tail, char trigger) { - ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - if (!doc.IsEditable) - { - Hide(); - return; - } - ScintillaControl sci = doc.SciControl; - sci.Focus(); - ReplaceText(sci, '\0'); + return completionList.ReplaceText(tail, trigger); } - + + #endregion + + #region Event Handling + /// - /// Filter the completion list with the letter typed + /// /// - static public void FindWordStartingWith(String word) + static public IntPtr GetHandle() + { + return completionList.GetHandle(); + } + + static public void OnChar(ScintillaControl sci, int value) + { + // Note: If we refactor/remove this class, this could be called directly from UITools + if (!completionList.OnChar((char)value)) + UITools.Manager.SendChar(sci, value); + } + + static public bool HandleKeys(ScintillaControl sci, Keys key) + { + return completionList.HandleKeys(key); + } + + private static void OnCancelHandler(Control sender, Int32 position, String text, Char trigger, ICompletionListItem item) + { + if (OnCancel != null) + OnCancel((ScintillaControl)sender, position, text, trigger, item); + } + + private static void OnInsertHandler(Control sender, Int32 position, String text, Char trigger, ICompletionListItem item) + { + if (OnInsert != null) + OnInsert((ScintillaControl)sender, position, text, trigger, item); + } + + #endregion + + #region Controls fading on Control key + + internal static void FadeOut() + { + completionList.FadeOut(); + } + + internal static void FadeIn() { - if (word == null) word = ""; - Int32 len = word.Length; - Int32 maxLen = 0; - Int32 lastScore = 0; - /// - /// FILTER ITEMS - /// - if (PluginBase.MainForm.Settings.AutoFilterList || fullList) + completionList.FadeIn(); + } + + #endregion + + #region Default Completion List Host + + internal class ScintillaHost : ICompletionListHost + { + + private List controlHierarchy = new List(); + + private WeakReference sci = new WeakReference(null); + internal ScintillaControl SciControl { - List found; - if (len == 0) + get { - found = allItems; - lastIndex = 0; - exactMatchInList = false; - smartMatchInList = true; - } - else - { - List temp = new List(allItems.Count); - Int32 n = allItems.Count; - Int32 i = 0; - Int32 score; - lastScore = 99; - ICompletionListItem item; - exactMatchInList = false; - smartMatchInList = false; - while (i < n) - { - item = allItems[i]; - // compare item's label with the searched word - score = SmartMatch(item.Label, word, len); - if (score > 0) - { - // first match found - if (!smartMatchInList || score < lastScore) - { - lastScore = score; - lastIndex = temp.Count; - smartMatchInList = true; - exactMatchInList = score < 5 && word == CompletionList.word; - } - temp.Add(new ItemMatch(score, item)); - if (item.Label.Length > maxLen) - { - widestLabel = item.Label; - maxLen = widestLabel.Length; - } - } - else if (fullList) temp.Add(new ItemMatch(0, item)); - i++; - } - // filter - found = new List(temp.Count); - for (int j = 0; j < temp.Count; j++) - { - if (j == lastIndex) lastIndex = found.Count; - if (temp[j].Score - lastScore < 3) found.Add(temp[j].Item); - } - } - // no match? - if (!smartMatchInList) - { - if (autoHideList && PluginBase.MainForm.Settings.EnableAutoHide && (len == 0 || len > 255)) - { - Hide('\0'); - } - else - { - // smart match - if (word.Length > 0) - { - FindWordStartingWith(word.Substring(0, len - 1)); - } - if (!smartMatchInList && autoHideList && PluginBase.MainForm.Settings.EnableAutoHide) - { - Hide('\0'); - } - } - return; + if (sci.Target == null) + return null; + + if (!sci.IsAlive) + return PluginBase.MainForm.CurrentDocument.SciControl; + + return (ScintillaControl)sci.Target; } - fullList = false; - // reset timer - if (tempo.Enabled) + set { - tempo.Enabled = false; - tempo.Enabled = true; + if (sci.Target == value) return; + + sci.Target = value; + ClearControlHierarchy(); } - // is update needed? - if (completionList.Items.Count == found.Count) + } + + public event EventHandler LostFocus + { + add { Owner.LostFocus += value; } + remove { Owner.LostFocus -= value; } + } + + private EventHandler positionChanged; + public event EventHandler PositionChanged + { + add { - int n = completionList.Items.Count; - bool changed = false; - for (int i = 0; i < n; i++) - { - if (completionList.Items[i] != found[i]) - { - changed = true; - break; - } - } - if (!changed) + if (positionChanged == null || positionChanged.GetInvocationList().Length == 0) { - // preselected item - if (defaultItem != null) - { - if (lastScore > 3 || (lastScore > 2 && defaultItem.Label.StartsWith(word, StringComparison.OrdinalIgnoreCase))) - { - lastIndex = lastIndex = TestDefaultItem(lastIndex, word, len); - } - } - completionList.SelectedIndex = lastIndex; - return; + var sci = SciControl; + sci.Scroll += Scintilla_Scroll; + sci.Zoom += Scintilla_Zoom; + + BuildControlHierarchy(sci); } + positionChanged += value; } - // update - try + remove { - completionList.BeginUpdate(); - completionList.Items.Clear(); - foreach (ICompletionListItem item in found) - { - completionList.Items.Add(item); - if (item.Label.Length > maxLen) - { - widestLabel = item.Label; - maxLen = widestLabel.Length; - } - } - Int32 topIndex = lastIndex; - if (defaultItem != null) + positionChanged -= value; + if (positionChanged == null || positionChanged.GetInvocationList().Length < 1) { - if (lastScore > 3 || (lastScore > 2 && defaultItem.Label.StartsWith(word, StringComparison.OrdinalIgnoreCase))) - { - lastIndex = TestDefaultItem(lastIndex, word, len); - } + var sci = SciControl; + sci.Scroll -= Scintilla_Scroll; + sci.Zoom -= Scintilla_Zoom; + ClearControlHierarchy(); } - // select first item - completionList.TopIndex = topIndex; - completionList.SelectedIndex = lastIndex; - } - catch (Exception ex) - { - Hide('\0'); - ErrorManager.ShowError(/*"Completion list populate error.", */ ex); - return; } - finally - { - completionList.EndUpdate(); - } - // update list - if (!tempo.Enabled) DisplayList(null, null); } - /// - /// NO FILTER - /// - else + + public event EventHandler SizeChanged { - int n = completionList.Items.Count; - ICompletionListItem item; - while (lastIndex < n) - { - item = completionList.Items[lastIndex] as ICompletionListItem; - if (String.Compare(item.Label, 0, word, 0, len, true) == 0) - { - completionList.SelectedIndex = lastIndex; - completionList.TopIndex = lastIndex; - exactMatchInList = true; - return; - } - lastIndex++; - } - // no match - if (autoHideList && PluginBase.MainForm.Settings.EnableAutoHide) Hide('\0'); - else exactMatchInList = false; + add { Owner.SizeChanged += value; } + remove { Owner.SizeChanged -= value; } } - } - private static int TestDefaultItem(Int32 index, String word, Int32 len) - { - if (defaultItem != null && completionList.Items.Contains(defaultItem)) + public event KeyEventHandler KeyDown { - Int32 score = (len == 0) ? 1 : SmartMatch(defaultItem.Label, word, len); - if (score > 0 && score < 6) return completionList.Items.IndexOf(defaultItem); + add { Owner.KeyDown += value; } + remove { Owner.KeyDown -= value; } } - return index; - } - static public int SmartMatch(string label, string word, int len) - { - if (label.Length < len) return 0; - - // simple matching - if (disableSmartMatch) + public event KeyEventHandler KeyPosted { - if (label.StartsWith(word, StringComparison.OrdinalIgnoreCase)) - { - if (label.StartsWithOrdinal(word)) return 1; - else return 5; - } - return 0; + add { SciControl.KeyPosted += value; } + remove { SciControl.KeyPosted -= value; } } - // try abbreviation - bool firstUpper = Char.IsUpper(word[0]); - if (firstUpper) + #pragma warning disable 0067 + public event KeyPressEventHandler KeyPress; // Unhandled for this one, although we could + #pragma warning restore 0067 + + public event MouseEventHandler MouseDown { - int abbr = IsAbbreviation(label, word); - if (abbr > 0) return abbr; + add { Owner.MouseDown += value; } + remove { Owner.MouseDown -= value; } } - - int p = label.IndexOf(word, StringComparison.OrdinalIgnoreCase); - if (p >= 0) - { - int p2; - if (firstUpper) // try case sensitive search - { - p2 = label.IndexOfOrdinal(word); - if (p2 >= 0) - { - int p3 = label.LastIndexOfOrdinal("." + word); // in qualified type name - if (p3 > 0) - { - if (p3 == label.LastIndexOf('.')) - { - if (label.EndsWithOrdinal("." + word)) return 1; - else return 3; - } - else return 4; - } - } - if (p2 == 0) - { - if (word == label) return 1; - else return 2; - } - else if (p2 > 0) return 4; - } - p2 = label.LastIndexOf("." + word, StringComparison.OrdinalIgnoreCase); // in qualified type name - if (p2 > 0) - { - if (p2 == label.LastIndexOf('.')) - { - if (label.EndsWith("." + word, StringComparison.OrdinalIgnoreCase)) return 2; - else return 4; - } - else return 5; - } - if (p == 0) - { - if (label.Equals(word, StringComparison.OrdinalIgnoreCase)) - { - if (label.Equals(word)) return 1; - else return 2; - } - else return 3; - } - else - { - int p4 = label.IndexOf(':'); - if (p4 > 0) return SmartMatch(label.Substring(p4 + 1), word, len); - return 5; - } + public Control Owner + { + get { return SciControl; } } - // loose - int firstChar = label.IndexOf(word[0].ToString(), StringComparison.OrdinalIgnoreCase); - int i = 1; - p = firstChar; - while (i < len && p >= 0) + public string SelectedText { - p = label.IndexOf(word[i++].ToString(), p + 1, StringComparison.OrdinalIgnoreCase); + get { return SciControl.SelText; } + set { SciControl.ReplaceSel(value); } } - return (p > 0) ? 7 : 0; - } - static public int IsAbbreviation(string label, string word) - { - int len = word.Length; - int i = 1; - char c = word[0]; - int p; - int p2; - int score = 0; - if (label[0] == c) { p2 = 0; score = 1; } - else if (label.IndexOf('.') < 0) + public int SelectionEnd { - p2 = label.IndexOf(c); - if (p2 < 0) return 0; - score = 3; + get { return SciControl.SelectionEnd; } + set { SciControl.SelectionStart = value; } } - else + + public int SelectionStart { - p2 = label.IndexOfOrdinal("." + c); - if (p2 >= 0) { score = 2; p2++; } - else - { - p2 = label.IndexOf(c); - if (p2 < 0) return 0; - score = 4; - } + get { return SciControl.SelectionStart; } + set { SciControl.SelectionStart = value; } } - int dist = 0; - while (i < len) + public int CurrentPos { - p = p2; - c = word[i++]; - if (Char.IsUpper(c)) p2 = label.IndexOfOrdinal(c.ToString(), p + 1); - else p2 = label.IndexOf(c.ToString(), p + 1, StringComparison.OrdinalIgnoreCase); - if (p2 < 0) return 0; - - int ups = 0; - for (int i2 = p + 1; i2 < p2; i2++) - if (label[i2] == '_') { ups = 0; } - else if (Char.IsUpper(label[i2])) ups++; - score += Math.Min(3, ups); // malus if skipped upper chars - - dist += p2 - p; + get { return SciControl.CurrentPos; } } - if (dist == len - 1) + + public bool IsEditable { - if (label == word || label.EndsWithOrdinal("." + word)) return 1; - return score; + get { return PluginBase.MainForm.CurrentDocument.IsEditable && SciControl != null; } } - else return score + 2; - } - /// - /// - /// - static public bool ReplaceText(ScintillaControl sci, char trigger) - { - return ReplaceText(sci, "", trigger); - } + public int GetLineHeight() + { + return UITools.Manager.LineHeight(SciControl); + } - /// - /// - /// - static public bool ReplaceText(ScintillaControl sci, String tail, char trigger) - { - sci.BeginUndoAction(); - try + public int GetLineFromCharIndex(int pos) { - String triggers = PluginBase.Settings.InsertionTriggers ?? ""; - if (triggers.Length > 0 && Regex.Unescape(triggers).IndexOf(trigger) < 0) return false; + return SciControl.LineFromPosition(pos); + } - ICompletionListItem item = null; - if (completionList.SelectedIndex >= 0) - { - item = completionList.Items[completionList.SelectedIndex] as ICompletionListItem; - } - Hide(); - if (item != null) - { - String replace = item.Value; - if (replace != null) - { - sci.SetSel(startPos, sci.CurrentPos); - if (word != null && tail.Length > 0) - { - if (replace.StartsWith(word, StringComparison.OrdinalIgnoreCase) && replace.IndexOfOrdinal(tail) >= word.Length) - { - replace = replace.Substring(0, replace.IndexOfOrdinal(tail)); - } - } - sci.ReplaceSel(replace); - if (OnInsert != null) OnInsert(sci, startPos, replace, trigger, item); - if (tail.Length > 0) sci.ReplaceSel(tail); - } - return true; - } - return false; + public Point GetPositionFromCharIndex(int pos) + { + var sci = SciControl; + return new Point(sci.PointXFromPosition(pos), sci.PointYFromPosition(pos)); } - finally + + public void SetSelection(int start, int end) { - sci.EndUndoAction(); + SciControl.SetSel(start, end); } - } - - #endregion - - #region Event Handling - - static public IntPtr GetHandle() - { - return completionList.Handle; - } - static public void OnChar(ScintillaControl sci, int value) - { - char c = (char)value; - string characterClass = ScintillaControl.Configuration.GetLanguage(sci.ConfigurationLanguage).characterclass.Characters; - if (characterClass.IndexOf(c) >= 0) + public void BeginUndoAction() { - word += c; - currentPos++; - FindWordStartingWith(word); - return; + SciControl.BeginUndoAction(); } - else if (noAutoInsert) + + public void EndUndoAction() { - CompletionList.Hide('\0'); - // handle this char - UITools.Manager.SendChar(sci, value); + SciControl.EndUndoAction(); } - else + + private void BuildControlHierarchy(Control current) { - // check for fast typing - long millis = (DateTime.Now.Ticks - showTime) / 10000; - if (!exactMatchInList && (word.Length > 0 || (millis < 400 && defaultItem == null))) + while (current != null) { - CompletionList.Hide('\0'); + current.LocationChanged += Control_LocationChanged; + current.ParentChanged += Control_ParentChanged; + controlHierarchy.Add(current); + current = current.Parent; } - else if (word.Length == 0 && (currentItem == null || currentItem == allItems[0]) && defaultItem == null) - { - CompletionList.Hide('\0'); - } - else if (word.Length > 0 || c == '.' || c == '(' || c == '[' || c == '<' || c == ',' || c == ';') + } + + private void ClearControlHierarchy() + { + foreach (var control in controlHierarchy) { - ReplaceText(sci, c.ToString(), c); + control.LocationChanged -= Control_LocationChanged; + control.ParentChanged -= Control_ParentChanged; } - // handle this char - UITools.Manager.SendChar(sci, value); + controlHierarchy.Clear(); } - } - static public bool HandleKeys(ScintillaControl sci, Keys key) - { - int index; - switch (key) + private void Control_LocationChanged(object sender, EventArgs e) { - case Keys.Back: - if (!UITools.CallTip.CallTipActive) sci.DeleteBack(); - if (word.Length > MinWordLength) - { - word = word.Substring(0, word.Length-1); - currentPos = sci.CurrentPos; - lastIndex = 0; - FindWordStartingWith(word); - } - else CompletionList.Hide((char)8); - return true; - - case Keys.Enter: - if (noAutoInsert || !ReplaceText(sci, '\n')) - { - CompletionList.Hide(); - return false; - } - return true; - - case Keys.Tab: - if (!ReplaceText(sci, '\t')) - { - CompletionList.Hide(); - return false; - } - return true; - - case Keys.Space: - if (noAutoInsert) CompletionList.Hide(); - return false; - - case Keys.Up: - noAutoInsert = false; - // the list was hidden and it should not appear - if (!completionList.Visible) - { - CompletionList.Hide(); - if (key == Keys.Up) sci.LineUp(); - else sci.CharLeft(); - return false; - } - // go up the list - if (completionList.SelectedIndex > 0) - { - RefreshTip(); - index = completionList.SelectedIndex-1; - completionList.SelectedIndex = index; - } - // wrap - else if (PluginBase.MainForm.Settings.WrapList) - { - RefreshTip(); - index = completionList.Items.Count-1; - completionList.SelectedIndex = index; - } - break; + if (positionChanged != null) + positionChanged(sender, e); + } - case Keys.Down: - noAutoInsert = false; - // the list was hidden and it should not appear - if (!completionList.Visible) - { - CompletionList.Hide(); - if (key == Keys.Down) sci.LineDown(); - else sci.CharRight(); - return false; - } - // go down the list - if (completionList.SelectedIndex < completionList.Items.Count-1) - { - RefreshTip(); - index = completionList.SelectedIndex+1; - completionList.SelectedIndex = index; - } - // wrap - else if (PluginBase.MainForm.Settings.WrapList) - { - RefreshTip(); - index = 0; - completionList.SelectedIndex = index; - } - break; + private void Control_ParentChanged(object sender, EventArgs e) + { + ClearControlHierarchy(); + BuildControlHierarchy(SciControl); + if (positionChanged != null) + positionChanged(sender, e); + } - case Keys.PageUp: - noAutoInsert = false; - // the list was hidden and it should not appear - if (!completionList.Visible) - { - CompletionList.Hide(); - sci.PageUp(); - return false; - } - // go up the list - if (completionList.SelectedIndex > 0) - { - RefreshTip(); - index = completionList.SelectedIndex-completionList.Height/completionList.ItemHeight; - if (index < 0) index = 0; - completionList.SelectedIndex = index; - } - break; + private void Scintilla_Scroll(object sender, ScrollEventArgs e) + { + if (positionChanged != null) + positionChanged(sender, e); + } - case Keys.PageDown: - noAutoInsert = false; - // the list was hidden and it should not appear - if (!completionList.Visible) - { - CompletionList.Hide(); - sci.PageDown(); - return false; - } - // go down the list - if (completionList.SelectedIndex < completionList.Items.Count-1) - { - RefreshTip(); - index = completionList.SelectedIndex+completionList.Height/completionList.ItemHeight; - if (index > completionList.Items.Count-1) index = completionList.Items.Count-1; - completionList.SelectedIndex = index; - } - break; - - case (Keys.Control | Keys.Space): - break; - - case Keys.Left: - sci.CharLeft(); - CompletionList.Hide(); - break; - - case Keys.Right: - sci.CharRight(); - CompletionList.Hide(); - break; - - default: - CompletionList.Hide(); - return false; + private void Scintilla_Zoom(ScintillaControl sci) + { + if (positionChanged != null) + positionChanged(sci, EventArgs.Empty); } - return true; - } - private static void RefreshTip() - { - UITools.Tip.Hide(); - tempoTip.Enabled = false; } #endregion - - #region Controls fading on Control key - - private static bool faded; - - internal static void FadeOut() - { - if (faded) return; - faded = true; - UITools.Tip.Hide(); - completionList.Visible = false; - } - - internal static void FadeIn() - { - if (!faded) return; - faded = false; - completionList.Visible = true; - } - - #endregion - } - - struct ItemMatch - { - public int Score; - public ICompletionListItem Item; - - public ItemMatch(int score, ICompletionListItem item) - { - Score = score; - Item = item; - } - } - } diff --git a/PluginCore/PluginCore/Controls/CompletionListControl.cs b/PluginCore/PluginCore/Controls/CompletionListControl.cs new file mode 100644 index 0000000000..29a84c6801 --- /dev/null +++ b/PluginCore/PluginCore/Controls/CompletionListControl.cs @@ -0,0 +1,1388 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using PluginCore.Managers; +using PluginCore.Helpers; + +namespace PluginCore.Controls +{ + public delegate void CompletionListInsertedTextHandler(Control sender, int position, string text, char trigger, ICompletionListItem item); + + public class CompletionListControl : IMessageFilter + { + public event CompletionListInsertedTextHandler OnInsert; + public event CompletionListInsertedTextHandler OnCancel; + public event CancelEventHandler OnShowing; + public event EventHandler OnHidden; + + /// + /// Properties of the class + /// + private System.Timers.Timer tempo; + private System.Timers.Timer tempoTip; + private System.Windows.Forms.ListBox completionList; + private System.Windows.Forms.ToolStripControlHost listContainer; + private System.Windows.Forms.ToolStripDropDown listHost; + private CompletionListWindow completionListWindow; + + #region State Properties + + private bool disableSmartMatch; + private ICompletionListItem currentItem; + private List allItems; + private bool exactMatchInList; + private bool smartMatchInList; + private bool autoHideList; + private bool noAutoInsert; + private bool isActive; + internal bool listUp; + private bool fullList; + private int startPos; + private int currentPos; + private int lastIndex; + private string currentWord; + private string word; + private bool needResize; + private string widestLabel; + private long showTime; + private ICompletionListItem defaultItem; + + private ICompletionListHost host; + private RichToolTip tip; + private MethodCallTip callTip; + + #endregion + + #region Control Creation + + /// + /// Creates the control + /// + public CompletionListControl(ICompletionListHost target) + { + if (target == null) + throw new ArgumentNullException("target"); + + this.host = target; + + listHost = new ToolStripDropDown(); + listHost.Padding = Padding.Empty; + listHost.Margin = Padding.Empty; + listHost.AutoClose = false; + listHost.DropShadowEnabled = false; + listHost.AutoSize = false; + listHost.Size = new Size(180, 100); + + tempo = new System.Timers.Timer(); + tempo.SynchronizingObject = (Form)PluginBase.MainForm; + tempo.Elapsed += new System.Timers.ElapsedEventHandler(DisplayList); + tempo.AutoReset = false; + tempoTip = new System.Timers.Timer(); + tempoTip.SynchronizingObject = (Form)PluginBase.MainForm; + tempoTip.Elapsed += new System.Timers.ElapsedEventHandler(UpdateTip); + tempoTip.AutoReset = false; + tempoTip.Interval = 800; + + completionList = new ListBoxEx(); + completionList.Font = new System.Drawing.Font(PluginBase.Settings.DefaultFont, FontStyle.Regular); + completionList.ItemHeight = completionList.Font.Height + 2; + completionList.DrawMode = DrawMode.OwnerDrawFixed; + completionList.DrawItem += new DrawItemEventHandler(CLDrawListItem); + completionList.Click += new EventHandler(CLClick); + completionList.DoubleClick += new EventHandler(CLDoubleClick); + + listContainer = new ToolStripControlHost(completionList); + listContainer.AutoToolTip = false; + listContainer.AutoSize = false; + listContainer.Margin = Padding.Empty; + listContainer.Padding = Padding.Empty; + + listHost.Items.Add(listContainer); + + CharacterClass = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + } + + #endregion + + #region Public List Properties + + /// + /// Is the control active? + /// + public bool Active + { + get { return isActive; } + } + + /// + /// Gets if the mouse is currently inside the completion list control + /// + public bool HasMouseIn + { + get + { + if (!isActive || completionList == null) return false; + return completionList.ClientRectangle.Contains(completionList.PointToClient(Control.MousePosition)); + } + } + + /// + /// Retrieves the currently selected label, or null if none selected + /// + public string SelectedLabel + { + get + { + if (completionList == null) return null; + ICompletionListItem selected = completionList.SelectedItem as ICompletionListItem; + return (selected == null) ? null : selected.Label; + } + } + + /// + /// Set to 0 after calling .Show to keep the completion list active + /// when the text was erased completely (using backspace) + /// + public int MinWordLength { get; set; } + + /// + /// Defines the characters allowed as part of a variable name + /// + public string CharacterClass { get; set; } + + /// + /// Gets the target of the current completion list control + /// + public ICompletionListHost Host + { + get { return host; } + } + + /// + /// Gets the help tip associated with the completion list + /// + public RichToolTip Tip + { + get + { + if (tip == null) + tip = new RichToolTip(host); + return tip; + } + // Allow injection of our own implementations + set { tip = value; } + } + + /// + /// Gets the method call tip associated with the completion list + /// + public MethodCallTip CallTip + { + get + { + if (callTip == null) + callTip = new MethodCallTip(this); + return callTip; + } + // Allow injection of our own implementations + set { callTip = value; } + } + + #endregion + + #region CompletionList Methods + + /// + /// Checks if the position is valid + /// + public bool CheckPosition(int position) + { + return position == currentPos; + } + + /// + /// Shows the completion list + /// + public void Show(List itemList, bool autoHide, string select) + { + if (!string.IsNullOrEmpty(select)) + { + int maxLen = 0; + foreach (ICompletionListItem item in itemList) + if (item.Label.Length > maxLen) maxLen = item.Label.Length; + maxLen = Math.Min(256, maxLen); + if (select.Length > maxLen) select = select.Substring(0, maxLen); + currentWord = select; + } + else currentWord = null; + Show(itemList, autoHide); + } + + /// + /// Shows the completion list + /// + public void Show(List itemList, bool autoHide) + { + try + { + if (!host.IsEditable) + { + if (isActive) Hide(); + return; + } + if ((itemList == null) || (itemList.Count == 0)) + { + if (isActive) Hide(); + return; + } + } + catch (Exception ex) + { + ErrorManager.ShowError(ex); + } + // state + allItems = itemList; + autoHideList = autoHide; + noAutoInsert = false; + word = ""; + if (currentWord != null) + { + word = currentWord; + currentWord = null; + } + MinWordLength = 1; + fullList = (word.Length == 0) || !autoHide || !PluginBase.MainForm.Settings.AutoFilterList; + lastIndex = 0; + exactMatchInList = false; + if (host.SelectionStart == host.SelectionEnd) + startPos = host.CurrentPos - word.Length; + else + startPos = host.SelectionStart; + currentPos = host.SelectionEnd; // sci.CurrentPos; + defaultItem = null; + // populate list + needResize = true; + tempo.Enabled = autoHide && (PluginBase.MainForm.Settings.DisplayDelay > 0); + if (tempo.Enabled) tempo.Interval = PluginBase.MainForm.Settings.DisplayDelay; + FindWordStartingWith(word); + // state + isActive = true; + tempoTip.Enabled = false; + showTime = DateTime.Now.Ticks; + disableSmartMatch = noAutoInsert || PluginBase.MainForm.Settings.DisableSmartMatch; + } + + /// + /// Set default selected item in completion list + /// + public void SelectItem(string name) + { + int p = name.IndexOf('<'); + if (p > 1) name = name.Substring(0, p) + ""; + string pname = (name.IndexOf('.') < 0) ? "." + name : null; + ICompletionListItem found = null; + foreach (ICompletionListItem item in completionList.Items) + { + if (item.Label == name) + { + defaultItem = item; + completionList.SelectedItem = item; + return; + } + if (pname != null && item.Label.EndsWith(pname)) found = item; + } + if (found != null) + { + defaultItem = found; + completionList.SelectedItem = found; + } + } + + /// + /// Require that completion items are explicitely inserted (Enter, Tab, mouse-click) + /// + public void DisableAutoInsertion() + { + noAutoInsert = true; + } + + /// + /// + /// + private void DisplayList(Object sender, System.Timers.ElapsedEventArgs e) + { + if (!host.IsEditable) return; + ListBox cl = completionList; + if (cl.Items.Count == 0) return; + + // measure control + var listSize = new Size(); + if (needResize && !string.IsNullOrEmpty(widestLabel)) + { + needResize = false; + Graphics g = cl.CreateGraphics(); + SizeF size = g.MeasureString(widestLabel, cl.Font); + listSize.Width = (int)Math.Min(Math.Max(size.Width + 40, 100), 400) + ScaleHelper.Scale(10); + } + else listSize.Width = cl.Width; + int newHeight = Math.Min(cl.Items.Count, 10) * cl.ItemHeight + 4; + listSize.Height = newHeight != cl.Height ? newHeight : cl.Height; + cl.Size = listContainer.Size = listHost.Size = listSize; + // place control + UpdatePosition(); + } + + public void Redraw() + { + Color back = PluginBase.MainForm.GetThemeColor("CompletionList.BackColor"); + completionList.BackColor = back == Color.Empty ? System.Drawing.SystemColors.Window : back; + } + + /// + /// Hide completion list + /// + public void Hide() + { + if (completionList != null && isActive) + { + RemoveHandlers(); + tempo.Enabled = false; + isActive = false; + fullList = false; + bool visible = listHost.Visible; + listHost.Close(); + if (completionList.Items.Count > 0) completionList.Items.Clear(); + currentItem = null; + allItems = null; + Tip.Hide(); + tempoTip.Enabled = false; + if (visible && OnHidden != null) OnHidden(this, EventArgs.Empty); + } + } + + /// + /// Cancel completion list with event + /// + public void Hide(char trigger) + { + if (completionList != null && isActive) + { + Hide(); + if (OnCancel != null) + { + if (!host.IsEditable) return; + OnCancel(host.Owner, currentPos, currentWord, trigger, null); + } + } + } + + /// + /// + /// + public void SelectWordInList(string tail) + { + if (!host.IsEditable) + { + Hide(); + return; + } + currentWord = tail; + currentPos += tail.Length; + host.SetSelection(currentPos, currentPos); + } + + /// + /// + /// + private void CLDrawListItem(Object sender, System.Windows.Forms.DrawItemEventArgs e) + { + ICompletionListItem item = completionList.Items[e.Index] as ICompletionListItem; + e.DrawBackground(); + Color fore = PluginBase.MainForm.GetThemeColor("CompletionList.ForeColor"); + bool selected = (e.State & DrawItemState.Selected) > 0; + Brush textBrush = (selected) ? SystemBrushes.HighlightText : fore == Color.Empty ? SystemBrushes.WindowText : new SolidBrush(fore); + Brush packageBrush = Brushes.Gray; + Rectangle tbounds = new Rectangle(ScaleHelper.Scale(18), e.Bounds.Top, e.Bounds.Width, e.Bounds.Height); + if (item != null) + { + Graphics g = e.Graphics; + float newHeight = e.Bounds.Height - 2; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.DrawImage(item.Icon, 1, e.Bounds.Top + ((e.Bounds.Height - newHeight) / 2), newHeight, newHeight); + int p = item.Label.LastIndexOf('.'); + if (p > 0 && !selected) + { + string package = item.Label.Substring(0, p + 1); + g.DrawString(package, e.Font, packageBrush, tbounds, StringFormat.GenericDefault); + int left = tbounds.Left + DrawHelper.MeasureDisplayStringWidth(e.Graphics, package, e.Font) - 2; + if (left < tbounds.Right) g.DrawString(item.Label.Substring(p + 1), e.Font, textBrush, left, tbounds.Top, StringFormat.GenericDefault); + } + else g.DrawString(item.Label, e.Font, textBrush, tbounds, StringFormat.GenericDefault); + } + e.DrawFocusRectangle(); + if ((item != null) && ((e.State & DrawItemState.Selected) > 0)) + { + Tip.Hide(); + currentItem = item; + tempoTip.Stop(); + tempoTip.Start(); + } + } + + /// + /// Display item information in tooltip + /// + public void UpdateTip(Object sender, System.Timers.ElapsedEventArgs e) + { + tempoTip.Stop(); + if (currentItem == null || listHost.Opacity != 1) + return; + + Tip.SetText(currentItem.Description ?? "", false); + Tip.Redraw(false); + + var screen = Screen.FromControl(listHost); + int rightWidth = screen.WorkingArea.Right - listHost.Right - 1; + int leftWidth = listHost.Left; + + Point posTarget = new Point(listHost.Right, listHost.Top); + int widthTarget = rightWidth; + if (rightWidth < 220 && leftWidth > 220) + { + widthTarget = leftWidth; + posTarget.X = listHost.Left - Tip.Size.Width; + } + + Tip.Location = posTarget; + Tip.Show(); + Tip.AutoSize(widthTarget, 500); + + if (widthTarget == leftWidth) + Tip.Location = new Point(listHost.Left - Tip.Size.Width, posTarget.Y); + } + + private void UpdatePosition() + { + Point coord = host.GetPositionFromCharIndex(startPos); + // Check for completion list outside of control view + if (coord.X < 0 || coord.X > host.Owner.Width || coord.Y < 0 || coord.Y > host.Owner.Height) + { + Hide(); + return; + } + coord = host.Owner.PointToScreen(coord); + var screen = Screen.FromHandle(host.Owner.Handle); + listUp = CallTip.CallTipActive || (coord.Y - listHost.Height > screen.WorkingArea.Top && coord.Y + host.GetLineHeight() + listHost.Height > screen.WorkingArea.Bottom); + if (listUp) coord.Y -= listHost.Height; + else coord.Y += host.GetLineHeight(); + // Keep on screen area + if (coord.X + listHost.Width > screen.WorkingArea.Right) + { + coord.X = screen.WorkingArea.Right - listHost.Width; + } + + if (listHost.Visible) + { + listHost.Show(coord); + if (Tip.Visible) UpdateTip(null, null); + } + else + { + Redraw(); + if (OnShowing != null) + { + var cancelArgs = new CancelEventArgs(); + OnShowing(this, cancelArgs); + if (cancelArgs.Cancel) + { + Hide(); + return; + } + } + listHost.Opacity = 1; + listHost.Show(coord); + if (CallTip.CallTipActive) CallTip.PositionControl(); + AddHandlers(); + } + + } + + /// + /// + /// + private void CLClick(Object sender, System.EventArgs e) + { + if (!host.IsEditable) + Hide(); + } + + /// + /// + /// + private void CLDoubleClick(Object sender, System.EventArgs e) + { + if (!host.IsEditable) + { + Hide(); + return; + } + ReplaceText('\0'); + } + + /// + /// Filter the completion list with the letter typed + /// + public void FindWordStartingWith(string word) + { + if (word == null) word = ""; + int len = word.Length; + int maxLen = 0; + int lastScore = 0; + // FILTER ITEMS + if (PluginBase.MainForm.Settings.AutoFilterList || fullList) + { + List found; + if (len == 0) + { + found = allItems; + lastIndex = 0; + exactMatchInList = false; + smartMatchInList = true; + } + else + { + List temp = new List(allItems.Count); + int n = allItems.Count; + int i = 0; + int score; + lastScore = 99; + ICompletionListItem item; + exactMatchInList = false; + smartMatchInList = false; + while (i < n) + { + item = allItems[i]; + // compare item's label with the searched word + score = SmartMatch(item.Label, word, len); + if (score > 0) + { + // first match found + if (!smartMatchInList || score < lastScore) + { + lastScore = score; + lastIndex = temp.Count; + smartMatchInList = true; + exactMatchInList = score < 5 && word == this.word; + } + temp.Add(new ItemMatch(score, item)); + if (item.Label.Length > maxLen) + { + widestLabel = item.Label; + maxLen = widestLabel.Length; + } + } + else if (fullList) temp.Add(new ItemMatch(0, item)); + i++; + } + // filter + found = new List(temp.Count); + for (int j = 0; j < temp.Count; j++) + { + if (j == lastIndex) lastIndex = found.Count; + if (temp[j].Score - lastScore < 3) found.Add(temp[j].Item); + } + } + // no match? + if (!smartMatchInList) + { + if (autoHideList && PluginBase.MainForm.Settings.EnableAutoHide && (len == 0 || len > 255)) + { + Hide('\0'); + } + else + { + // smart match + if (word.Length > 0) + { + FindWordStartingWith(word.Substring(0, len - 1)); + } + if (!smartMatchInList && autoHideList && PluginBase.MainForm.Settings.EnableAutoHide) + { + Hide('\0'); + } + } + return; + } + fullList = false; + // reset timer + if (tempo.Enabled) + { + tempo.Enabled = false; + tempo.Enabled = true; + } + // is update needed? + if (completionList.Items.Count == found.Count) + { + int n = completionList.Items.Count; + bool changed = false; + for (int i = 0; i < n; i++) + { + if (completionList.Items[i] != found[i]) + { + changed = true; + break; + } + } + if (!changed) + { + // preselected item + if (defaultItem != null) + { + if (lastScore > 3 || (lastScore > 2 && defaultItem.Label.StartsWith(word, StringComparison.OrdinalIgnoreCase))) + { + lastIndex = lastIndex = TestDefaultItem(lastIndex, word, len); + } + } + completionList.SelectedIndex = lastIndex; + return; + } + } + // update + try + { + completionList.BeginUpdate(); + completionList.Items.Clear(); + foreach (ICompletionListItem item in found) + { + completionList.Items.Add(item); + if (item.Label.Length > maxLen) + { + widestLabel = item.Label; + maxLen = widestLabel.Length; + } + } + int topIndex = lastIndex; + if (defaultItem != null) + { + if (lastScore > 3 || (lastScore > 2 && defaultItem.Label.StartsWith(word, StringComparison.OrdinalIgnoreCase))) + { + lastIndex = TestDefaultItem(lastIndex, word, len); + } + } + // select first item + completionList.TopIndex = topIndex; + completionList.SelectedIndex = lastIndex; + } + catch (Exception ex) + { + Hide('\0'); + ErrorManager.ShowError(/*"Completion list populate error.", */ ex); + return; + } + finally + { + completionList.EndUpdate(); + } + // update list + if (!tempo.Enabled) DisplayList(null, null); + } + // NO FILTER + else + { + int n = completionList.Items.Count; + ICompletionListItem item; + while (lastIndex < n) + { + item = completionList.Items[lastIndex] as ICompletionListItem; + if (string.Compare(item.Label, 0, word, 0, len, true) == 0) + { + completionList.SelectedIndex = lastIndex; + completionList.TopIndex = lastIndex; + exactMatchInList = true; + return; + } + lastIndex++; + } + // no match + if (autoHideList && PluginBase.MainForm.Settings.EnableAutoHide) Hide('\0'); + else exactMatchInList = false; + } + } + + private int TestDefaultItem(int index, string word, int len) + { + if (defaultItem != null && completionList.Items.Contains(defaultItem)) + { + int score = (len == 0) ? 1 : SmartMatch(defaultItem.Label, word, len); + if (score > 0 && score < 6) return completionList.Items.IndexOf(defaultItem); + } + return index; + } + + public int SmartMatch(string label, string word, int len) + { + if (label.Length < len) return 0; + + // simple matching + if (disableSmartMatch) + { + if (label.StartsWith(word, StringComparison.OrdinalIgnoreCase)) + { + if (label.StartsWithOrdinal(word)) return 1; + else return 5; + } + return 0; + } + + // try abbreviation + bool firstUpper = char.IsUpper(word[0]); + if (firstUpper) + { + int abbr = IsAbbreviation(label, word); + if (abbr > 0) return abbr; + } + + int p = label.IndexOf(word, StringComparison.OrdinalIgnoreCase); + if (p >= 0) + { + int p2; + if (firstUpper) // try case sensitive search + { + p2 = label.IndexOfOrdinal(word); + if (p2 >= 0) + { + int p3 = label.LastIndexOfOrdinal("." + word); // in qualified type name + if (p3 > 0) + { + if (p3 == label.LastIndexOf('.')) + { + if (label.EndsWithOrdinal("." + word)) return 1; + else return 3; + } + else return 4; + } + } + if (p2 == 0) + { + if (word == label) return 1; + else return 2; + } + else if (p2 > 0) return 4; + } + + p2 = label.LastIndexOf("." + word, StringComparison.OrdinalIgnoreCase); // in qualified type name + if (p2 > 0) + { + if (p2 == label.LastIndexOf('.')) + { + if (label.EndsWith("." + word, StringComparison.OrdinalIgnoreCase)) return 2; + else return 4; + } + else return 5; + } + if (p == 0) + { + if (label.Equals(word, StringComparison.OrdinalIgnoreCase)) + { + if (label.Equals(word)) return 1; + else return 2; + } + else return 3; + } + else + { + int p4 = label.IndexOf(':'); + if (p4 > 0) return SmartMatch(label.Substring(p4 + 1), word, len); + return 5; + } + } + + // loose + int n = label.Length; + int firstChar = label.IndexOf(word[0].ToString(), StringComparison.OrdinalIgnoreCase); + int i = 1; + p = firstChar; + while (i < len && p >= 0) + { + p = label.IndexOf(word[i++].ToString(), p + 1, StringComparison.OrdinalIgnoreCase); + } + return (p > 0) ? 7 : 0; + } + + public int IsAbbreviation(string label, string word) + { + int len = word.Length; + int i = 1; + char c = word[0]; + int p; + int p2; + int score = 0; + if (label[0] == c) { p2 = 0; score = 1; } + else if (label.IndexOf('.') < 0) + { + p2 = label.IndexOf(c); + if (p2 < 0) return 0; + score = 3; + } + else + { + p2 = label.IndexOfOrdinal("." + c); + if (p2 >= 0) { score = 2; p2++; } + else + { + p2 = label.IndexOf(c); + if (p2 < 0) return 0; + score = 4; + } + } + int dist = 0; + + while (i < len) + { + p = p2; + c = word[i++]; + if (char.IsUpper(c)) p2 = label.IndexOfOrdinal(c.ToString(), p + 1); + else p2 = label.IndexOf(c.ToString(), p + 1, StringComparison.OrdinalIgnoreCase); + if (p2 < 0) return 0; + + int ups = 0; + for (int i2 = p + 1; i2 < p2; i2++) + if (label[i2] == '_') { ups = 0; } + else if (char.IsUpper(label[i2])) ups++; + score += Math.Min(3, ups); // malus if skipped upper chars + + dist += p2 - p; + } + if (dist == len - 1) + { + if (label == word || label.EndsWithOrdinal("." + word)) return 1; + return score; + } + else return score + 2; + } + + /// + /// + /// + public bool ReplaceText(char trigger) + { + return ReplaceText("", trigger); + } + + /// + /// + /// + public bool ReplaceText(string tail, char trigger) + { + String triggers = PluginBase.Settings.InsertionTriggers ?? ""; + if (triggers.Length > 0 && Regex.Unescape(triggers).IndexOf(trigger) < 0) return false; + + try + { + ICompletionListItem item = null; + if (completionList.SelectedIndex >= 0) + { + item = completionList.Items[completionList.SelectedIndex] as ICompletionListItem; + } + Hide(); + if (item != null) + { + String replace = item.Value; + if (replace != null) + { + if (word != null && tail.Length > 0) + { + if (replace.StartsWith(word, StringComparison.OrdinalIgnoreCase) && replace.IndexOfOrdinal(tail) >= word.Length) + { + replace = replace.Substring(0, replace.IndexOfOrdinal(tail)); + } + } + host.BeginUndoAction(); + host.SetSelection(startPos, host.CurrentPos); + host.SelectedText = replace; + if (OnInsert != null) OnInsert(host.Owner, startPos, replace, trigger, item); + if (tail.Length > 0) host.SelectedText = tail; + } + return true; + } + return false; + } + finally + { + host.EndUndoAction(); + } + } + + #endregion + + #region Event Handling + + /// + /// + /// + public IntPtr GetHandle() + { + return completionList.Handle; + } + + private void AddHandlers() + { + if (!CallTip.CallTipActive) + Application.AddMessageFilter(this); + host.LostFocus += Target_LostFocus; + host.MouseDown += Target_MouseDown; + host.KeyDown += Target_KeyDown; + host.KeyPress += Target_KeyPress; + host.PositionChanged += Target_PositionChanged; + host.SizeChanged += Target_SizeChanged; + + completionListWindow = new CompletionListWindow(this); + } + + private void RemoveHandlers() + { + if (!CallTip.CallTipActive) + Application.RemoveMessageFilter(this); + host.LostFocus -= Target_LostFocus; + host.MouseDown -= Target_MouseDown; + host.KeyDown -= Target_KeyDown; + host.KeyPress -= Target_KeyPress; + host.PositionChanged -= Target_PositionChanged; + host.SizeChanged -= Target_SizeChanged; + + if (completionListWindow != null) + completionListWindow.ReleaseHandle(); + completionListWindow = null; + } + + private void Target_LostFocus(object sender, EventArgs e) + { + if (!listHost.ContainsFocus && !Tip.Focused && !CallTip.Focused && !host.Owner.ContainsFocus) + Hide(); + } + + private void Target_MouseDown(object sender, MouseEventArgs e) + { + if (host.CurrentPos != currentPos) + Hide(); + } + + private void Target_KeyDown(object sender, KeyEventArgs e) + { + if (!e.Handled) + e.SuppressKeyPress = e.Handled = HandleKeys(e.KeyData); + } + + private void Target_KeyPress(object sender, KeyPressEventArgs e) + { + if (!char.IsControl(e.KeyChar) && !e.Handled) + { + // Hacky... the current implementation relies on the OnChar Scintilla event, which happens after the KeyPress event + // We either create an OnChar event in ICompletionListHost and implement it, or change the current behaviour + e.Handled = true; + host.SelectedText = new string(e.KeyChar, 1); + int pos = host.CurrentPos + 1; + host.SetSelection(pos, pos); + + OnChar(e.KeyChar); + } + } + + private void Target_PositionChanged(object sender, EventArgs e) + { + UpdatePosition(); + } + + private void Target_SizeChanged(object sender, EventArgs e) + { + Point coord = host.GetPositionFromCharIndex(startPos); + // Check for completion list outside of control view + if (coord.X < 0 || coord.X > host.Owner.Width || coord.Y < 0 || coord.Y > host.Owner.Height) + Hide(); + } + + /// + /// + /// + public bool OnChar(char c) + { + if (CharacterClass.IndexOf(c) >= 0) + { + word += c; + currentPos++; + FindWordStartingWith(word); + return true; + } + else if (noAutoInsert) + { + Hide('\0'); + // handle this char + return false; + } + else + { + // check for fast typing + long millis = (DateTime.Now.Ticks - showTime) / 10000; + if (!exactMatchInList && (word.Length > 0 || (millis < 400 && defaultItem == null))) + { + Hide('\0'); + } + else if (word.Length == 0 && (currentItem == null || currentItem == allItems[0]) && defaultItem == null) + { + Hide('\0'); + } + else if (word.Length > 0 || c == '.' || c == '(' || c == '[' || c == '<' || c == ',' || c == ';') + { + ReplaceText(c.ToString(), c); + } + // handle this char + return false; + } + } + + /// + /// + /// + public bool HandleKeys(Keys key) + { + int index; + switch (key) + { + case Keys.Back: + if (word.Length > MinWordLength) + { + word = word.Substring(0, word.Length - 1); + currentPos = host.CurrentPos - 1; + lastIndex = 0; + FindWordStartingWith(word); + } + else Hide((char)8); + return false; + + case Keys.Enter: + if (noAutoInsert || !ReplaceText('\n')) + { + Hide(); + return false; + } + return true; + + case Keys.Tab: + if (!ReplaceText('\t')) + { + Hide(); + return false; + } + return true; + + case Keys.Space: + if (noAutoInsert) Hide(); + return false; + + case Keys.Up: + noAutoInsert = false; + // the list was hidden and it should not appear + if (!listHost.Visible) + { + Hide(); + return false; + } + // go up the list + if (completionList.SelectedIndex > 0) + { + RefreshTip(); + index = completionList.SelectedIndex - 1; + completionList.SelectedIndex = index; + } + // wrap + else if (PluginBase.MainForm.Settings.WrapList) + { + RefreshTip(); + index = completionList.Items.Count - 1; + completionList.SelectedIndex = index; + } + break; + + case Keys.Down: + noAutoInsert = false; + // the list was hidden and it should not appear + if (!listHost.Visible) + { + Hide(); + return false; + } + // go down the list + if (completionList.SelectedIndex < completionList.Items.Count - 1) + { + RefreshTip(); + index = completionList.SelectedIndex + 1; + completionList.SelectedIndex = index; + } + // wrap + else if (PluginBase.MainForm.Settings.WrapList) + { + RefreshTip(); + index = 0; + completionList.SelectedIndex = index; + } + break; + + case Keys.PageUp: + /*case Keys.PageUp | Keys.Control:*/ + // Used to navigate through documents + noAutoInsert = false; + // the list was hidden and it should not appear + if (!listHost.Visible) + { + Hide(); + return false; + } + // go up the list + if (completionList.SelectedIndex > 0) + { + RefreshTip(); + index = completionList.SelectedIndex - completionList.Height / completionList.ItemHeight; + if (index < 0) index = 0; + completionList.SelectedIndex = index; + } + break; + + case Keys.PageDown: + /*case Keys.PageDown | Keys.Control:*/ + // Used to navigate through documents + noAutoInsert = false; + // the list was hidden and it should not appear + if (!listHost.Visible) + { + Hide(); + return false; + } + // go down the list + if (completionList.SelectedIndex < completionList.Items.Count - 1) + { + RefreshTip(); + index = completionList.SelectedIndex + completionList.Height / completionList.ItemHeight; + if (index > completionList.Items.Count - 1) index = completionList.Items.Count - 1; + completionList.SelectedIndex = index; + } + break; + + case Keys.Home: + case Keys.End: + Hide(); + return false; + /* These could be interesting with some shortcut or condition... + noAutoInsert = false; + // go down the list + if (completionList.SelectedIndex > 0) + { + RefreshTip(); + index = 0; + completionList.SelectedIndex = index; + } + + break; + + case Keys.End: + noAutoInsert = false; + // go down the list + if (completionList.SelectedIndex < completionList.Items.Count - 1) + { + RefreshTip(); + index = completionList.Items.Count - 1; + completionList.SelectedIndex = index; + } + + break;*/ + + case (Keys.Control | Keys.Space): + break; + + case Keys.Left: + case Keys.Right: + Hide(); + return false; + + case Keys.Escape: + Hide((char) 27); + break; + + default: + Keys modifiers = key & Keys.Modifiers; + if (modifiers == Keys.Control) + { + key = key & Keys.KeyCode; + if (key > 0 && key != Keys.ControlKey && key != Keys.Down && key != Keys.Up) + Hide(); + } + else if (modifiers == Keys.Shift) + { + key = key & Keys.KeyCode; + if (key == Keys.Down || key == Keys.Up || key == Keys.Left || key == Keys.Right || + key == Keys.PageUp || key == Keys.PageDown || key == Keys.Home || key == Keys.End) + Hide(); + } + else if (modifiers == (Keys.Shift | Keys.Control)) + { + key = key & Keys.KeyCode; + if (key == Keys.Left || key == Keys.Right) + Hide(); + } + + return false; + } + return true; + } + + private void RefreshTip() + { + Tip.Hide(); + tempoTip.Enabled = false; + } + + #endregion + + #region Controls fading on Control key + + internal void FadeOut() + { + if (listHost.Opacity != 1) return; + Tip.Hide(); + listHost.Opacity = 0; + } + + internal void FadeIn() + { + if (listHost.Opacity == 1) return; + listHost.Opacity = 1; + } + + #endregion + + #region Global Hook + + public bool PreFilterMessage(ref Message m) + { + if (m.Msg == Win32.WM_MOUSEWHEEL) // capture all MouseWheel events + { + if (Tip.Focused || CallTip.Focused) return false; + if (Win32.ShouldUseWin32()) + { + Win32.SendMessage(completionList.Handle, m.Msg, (Int32)m.WParam, (Int32)m.LParam); + return true; + } + } + else if (m.Msg == Win32.WM_KEYDOWN) + { + if ((int)m.WParam == 17) // Ctrl + { + if (Tip.Focused || CallTip.Focused) return false; + if (Active) FadeOut(); + if (CallTip.CallTipActive) CallTip.FadeOut(); + } + else if ((int) m.WParam == 112) // F1 - since it's by default set as a shortcut we are required to handle it at a lower level + { + UITools.Manager.ShowDetails = !UITools.Manager.ShowDetails; + bool retVal = false; + if (CallTip.Visible) + { + callTip.UpdateTip(); + retVal = true; + } + if (Active) + { + UpdateTip(null, null); + retVal = true; + } + else if (Tip.Visible) + { + Tip.UpdateTip(); + retVal = true; + } + + return retVal; + } + } + else if (m.Msg == Win32.WM_KEYUP) + { + if (Tip.Focused || CallTip.Focused) return false; + if ((int)m.WParam == 17 || (int)m.WParam == 18) // Ctrl / AltGr + { + if (Active) FadeIn(); + if (CallTip.CallTipActive) CallTip.FadeIn(); + } + } + return false; + } + + private class CompletionListWindow : NativeWindow + { + private const int WM_ACTIVATEAPP = 0x1C; + + private CompletionListControl owner; + + public CompletionListWindow(CompletionListControl owner) + { + this.owner = owner; + AssignHandle(owner.listHost.Handle); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_ACTIVATEAPP && m.WParam == IntPtr.Zero) + owner.Hide(); + base.WndProc(ref m); + } + } + + #endregion + + #region Unfocusable List + + // If by any chance this is not compatible with CrossOver, or we want some alternative 100% crossplatform compatible, a custom fully managed control that cannot be focused could be developed + private class ListBoxEx : ListBox + { + protected override void DefWndProc(ref Message m) + { + const int WM_MOUSEACTIVATE = 0x21; + const int WM_LBUTTONDOWN = 0x201; + const int WM_LBUTTONDBLCLK = 0x203; + const int MA_NOACTIVATE = 0x0003; + + switch (m.Msg) + { + case WM_MOUSEACTIVATE: + m.Result = (IntPtr)MA_NOACTIVATE; + return; + case WM_LBUTTONDOWN: + SelectedIndex = IndexFromPoint((short)(m.LParam.ToInt32() & 0xFFFF), (short)((m.LParam.ToInt32() & 0xFFFF0000) >> 16)); + m.Result = IntPtr.Zero; + return; + case WM_LBUTTONDBLCLK: + m.Result = IntPtr.Zero; + return; + } + base.DefWndProc(ref m); + } + } + + #endregion + + } + + struct ItemMatch + { + public int Score; + public ICompletionListItem Item; + + public ItemMatch(int score, ICompletionListItem item) + { + Score = score; + Item = item; + } + } + +} diff --git a/PluginCore/PluginCore/Controls/ICompletionListHost.cs b/PluginCore/PluginCore/Controls/ICompletionListHost.cs new file mode 100644 index 0000000000..db315c045c --- /dev/null +++ b/PluginCore/PluginCore/Controls/ICompletionListHost.cs @@ -0,0 +1,42 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PluginCore.Controls +{ + /* Possible properties/methods of interest: + * - suppressedKeys: collection of extra key combinations that would be consumed by the completionList + * - OnListShowing/OnListHidden: method to know when the list is going to show or is hidden, better than getting a reference to the list and listen for events + * - AfterCompletionCommit/BeforeCompletionCommit + */ + public interface ICompletionListHost + { + + event EventHandler LostFocus; + event EventHandler PositionChanged; + event EventHandler SizeChanged; + event KeyEventHandler KeyDown; + // Hacky event... needed for MethodCallTip where we need to get the new state after the key has been sent + // A better approach, and the way some IDEs work, would require MethodCallTip to be more "active" having more knowledge about the written function data, as well as a timer for some operations + event KeyEventHandler KeyPosted; + event KeyPressEventHandler KeyPress; + event MouseEventHandler MouseDown; + + Control Owner { get; } + string SelectedText { get; set; } + int SelectionEnd { get; set; } + int SelectionStart { get; set; } + int CurrentPos { get; } + bool IsEditable { get; } + + int GetLineFromCharIndex(int pos); + Point GetPositionFromCharIndex(int pos); + int GetLineHeight(); + void SetSelection(int start, int end); + + void BeginUndoAction(); + void EndUndoAction(); + + } + +} diff --git a/PluginCore/PluginCore/Controls/InactiveForm.cs b/PluginCore/PluginCore/Controls/InactiveForm.cs new file mode 100644 index 0000000000..f5954a3344 --- /dev/null +++ b/PluginCore/PluginCore/Controls/InactiveForm.cs @@ -0,0 +1,67 @@ +using System.Windows.Forms; + +namespace PluginCore.Controls +{ + /// + /// A form that, unless forced directly by code, does not become the foreground window when shown or clicked + /// + public class InactiveForm : Form + { + + private const int WS_EX_TOPMOST = 0x8; + private const int WS_EX_NOACTIVATE = 0x8000000; + + protected override bool ShowWithoutActivation + { + get { return true; } + } + + private bool topMost; + /// + /// Determines whether the form should display as top most. Unlike Form.TopMost, this does not give focus + /// + public new bool TopMost + { + get { return topMost; } + set + { + if (topMost == value) return; + topMost = value; + if (IsHandleCreated) RecreateHandle(); + } + } + + private bool noActivate; + /// + /// Gets or sets if the form can become the foreground window. In the case of a top level window this only works for + /// other applications, not within the same one. + /// + public bool NoActivate + { + get { return noActivate; } + set + { + if (noActivate == value) return; + noActivate = value; + if (IsHandleCreated) RecreateHandle(); + } + } + + protected override CreateParams CreateParams + { + get + { + CreateParams p = base.CreateParams; + + if (noActivate) + p.ExStyle |= WS_EX_NOACTIVATE; + + if (topMost) + p.ExStyle |= WS_EX_TOPMOST; + + return p; + } + } + + } +} diff --git a/PluginCore/PluginCore/Controls/MethodCallTip.cs b/PluginCore/PluginCore/Controls/MethodCallTip.cs index 98c117c665..c22349ff98 100644 --- a/PluginCore/PluginCore/Controls/MethodCallTip.cs +++ b/PluginCore/PluginCore/Controls/MethodCallTip.cs @@ -1,25 +1,23 @@ +using System; using System.Drawing; using System.Windows.Forms; using PluginCore.Utilities; -using ScintillaNet; namespace PluginCore.Controls { public class MethodCallTip: RichToolTip { - public delegate void UpdateCallTipHandler(ScintillaControl sender, int position); + public delegate void UpdateCallTipHandler(Control sender, int position); // events public event UpdateCallTipHandler OnUpdateCallTip; - public static string HLTextStyleBeg = "[B]"; public static string HLTextStyleEnd = "[/B]"; public static string HLBgStyleBeg = "[BGCOLOR=#000:OVERLAY]"; public static string HLBgStyleEnd = "[/BGCOLOR]"; - // state protected string currentText; protected int currentHLStart; @@ -28,11 +26,15 @@ public class MethodCallTip: RichToolTip protected int memberPos; protected int startPos; protected int currentPos; - protected int deltaPos; protected int currentLine; - public MethodCallTip(IMainForm mainForm): base(mainForm) + protected CompletionListControl completionList; + + public MethodCallTip(CompletionListControl owner): base(owner.Host) { + completionList = owner; + host.VisibleChanged += Host_VisibleChanged; + Color color = PluginBase.MainForm.GetThemeColor("MethodCallTip.SelectedBack"); Color fore = PluginBase.MainForm.GetThemeColor("MethodCallTip.SelectedFore"); if (color != Color.Empty) HLBgStyleBeg = "[BGCOLOR=" + DataConverter.ColorToHex(color).Replace("0x", "#") + "]"; @@ -48,19 +50,24 @@ public bool CallTipActive get { return isActive; } } - public bool Focused + public int CurrentHLStart + { + get { return currentHLStart; } + } + + public int CurrentHLEnd + { + get { return currentHLEnd; } + } + + public int MemberPosition { - get { return toolTipRTB.Focused; } + get { return memberPos; } } public override void Hide() { - if (isActive) - { - isActive = false; - UITools.Manager.UnlockControl(); // unlock keys - } - faded = false; + isActive = false; currentText = null; currentHLStart = -1; currentHLEnd = -1; @@ -72,47 +79,60 @@ public bool CheckPosition(int position) return position == currentPos; } - public void CallTipShow(ScintillaControl sci, int position, string text) + public void CallTipShow(int position, string text) { - CallTipShow(sci, position, text, true); + CallTipShow(position, text, true); } - public void CallTipShow(ScintillaControl sci, int position, string text, bool redraw) + public void CallTipShow(int position, string text, bool redraw) { - if (toolTip.Visible && position == memberPos && text == currentText) + if (host.Visible && position == memberPos && text == currentText) + { + if (owner.GetLineFromCharIndex(owner.CurrentPos) != currentLine) + PositionControl(); + return; + } - toolTip.Visible = false; + host.Visible = false; currentText = text; + currentHLEnd = currentHLStart = -1; SetText(text, true); memberPos = position; startPos = memberPos + toolTipRTB.Text.IndexOf('('); - currentPos = sci.CurrentPos; - deltaPos = startPos - currentPos + 1; - currentLine = sci.CurrentLine; - PositionControl(sci); + PositionControl(); + Show(); // state isActive = true; - faded = false; - UITools.Manager.LockControl(sci); } - public void PositionControl(ScintillaControl sci) + public void PositionControl() { + currentPos = owner.CurrentPos; + currentLine = owner.GetLineFromCharIndex(currentPos); // compute control location - Point p = new Point(sci.PointXFromPosition(memberPos), sci.PointYFromPosition(memberPos)); - p = ((Form)PluginBase.MainForm).PointToClient(sci.PointToScreen(p)); - toolTip.Left = p.X /*+ sci.Left*/; - bool hasListUp = !CompletionList.Active || CompletionList.listUp; - if (currentLine > sci.LineFromPosition(memberPos) || !hasListUp) toolTip.Top = p.Y - toolTip.Height /*+ sci.Top*/; - else toolTip.Top = p.Y + UITools.Manager.LineHeight(sci) /*+ sci.Top*/; - // Keep on control area - if (toolTip.Right > ((Form)PluginBase.MainForm).ClientRectangle.Right) + Point p = owner.GetPositionFromCharIndex(memberPos); + p.Y = owner.GetPositionFromCharIndex(currentPos).Y; + if (p.Y < 0 || p.Y > owner.Owner.Height || p.X < 0 || p.X > owner.Owner.Width) { - toolTip.Left = ((Form)PluginBase.MainForm).ClientRectangle.Right - toolTip.Width; + Hide(); + return; + } + p = owner.Owner.PointToScreen(p); + host.Left = p.X /*+ sci.Left*/; + bool hasListUp = !completionList.Active || completionList.listUp; + if (!hasListUp) host.Top = p.Y - host.Height /*+ sci.Top*/; + else host.Top = p.Y + owner.GetLineHeight(); + // Keep on screen area + var screen = Screen.FromControl(owner.Owner); + if (host.Right > screen.WorkingArea.Right) + { + host.Left = screen.WorkingArea.Right - host.Width; + } + if (host.Left < 0) + { + host.Left = 0; } - toolTip.Show(); - toolTip.BringToFront(); } public void CallTipSetHlt(int start, int end) @@ -122,7 +142,11 @@ public void CallTipSetHlt(int start, int end) public void CallTipSetHlt(int start, int end, bool forceRedraw) { if (currentHLStart == start && currentHLEnd == end) + { + if (owner.GetLineFromCharIndex(owner.CurrentPos) != currentLine) + PositionControl(); return; + } currentHLStart = start; currentHLEnd = end; @@ -150,109 +174,187 @@ public void CallTipSetHlt(int start, int end, bool forceRedraw) } } + private void Host_VisibleChanged(object sender, EventArgs e) + { + if (host.Visible) + { + owner.KeyDown += Target_KeyDown; + owner.KeyPosted += Target_KeyPosted; + owner.KeyPress += Target_KeyPress; + owner.PositionChanged += Target_PositionChanged; + owner.SizeChanged += Target_SizeChanged; + owner.LostFocus += Target_LostFocus; + owner.MouseDown += Target_MouseDown; + + if (!completionList.Active) + Application.AddMessageFilter(completionList); + } + else + { + owner.KeyDown -= Target_KeyDown; + owner.KeyPosted -= Target_KeyPosted; + owner.KeyPress -= Target_KeyPress; + owner.PositionChanged -= Target_PositionChanged; + owner.SizeChanged -= Target_SizeChanged; + owner.LostFocus -= Target_LostFocus; + owner.MouseDown -= Target_MouseDown; + + if (!completionList.Active) + Application.RemoveMessageFilter(completionList); + } + } + + private void Target_KeyDown(object sender, KeyEventArgs e) + { + if (!e.Handled) + e.SuppressKeyPress = e.Handled = HandleKeys(e.KeyData); + } + + private void Target_KeyPosted(object sender, KeyEventArgs e) + { + HandlePostedKeys(e.KeyData); + } + + private void Target_KeyPress(object sender, KeyPressEventArgs e) + { + if (!char.IsControl(e.KeyChar)) + OnChar(e.KeyChar); + } + + private void Target_PositionChanged(object sender, EventArgs e) + { + PositionControl(); + } + + private void Target_SizeChanged(object sender, EventArgs e) + { + Point p = owner.GetPositionFromCharIndex(memberPos); + p.Y = owner.GetPositionFromCharIndex(currentPos).Y; + if (p.Y < 0 || p.Y > owner.Owner.Height || p.X < 0 || p.X > owner.Owner.Width) + Hide(); + } + + private void Target_LostFocus(object sender, EventArgs e) + { + if (!Focused && !completionList.Tip.Focused) + Hide(); + } + + private void Target_MouseDown(object sender, MouseEventArgs e) + { + if (owner.CurrentPos != currentPos) + Hide(); + } + #region Keys handling - public void OnChar(ScintillaControl sci, int value) + public void OnChar(int value) { currentPos++; - UpdateTip(sci); + UpdateTip(); } - public new void UpdateTip(ScintillaControl sci) + public override void UpdateTip() { - if (OnUpdateCallTip != null) OnUpdateCallTip(sci, currentPos); + if (CallTipActive && OnUpdateCallTip != null) OnUpdateCallTip(owner.Owner, currentPos); } - public bool HandleKeys(ScintillaControl sci, Keys key) + public bool HandleKeys(Keys key) { switch (key) { - case Keys.Multiply: - case Keys.Subtract: - case Keys.Divide: - case Keys.Decimal: - case Keys.Add: - return false; + case Keys.PageDown: + case Keys.PageUp: + if (!completionList.Active) + Hide(); + break; - case Keys.Up: - if (!CompletionList.Active) sci.LineUp(); - return false; - case Keys.Down: - if (!CompletionList.Active) sci.LineDown(); - return false; case Keys.Up | Keys.Shift: - sci.LineUpExtend(); - return false; case Keys.Down | Keys.Shift: - sci.LineDownExtend(); - return false; - case Keys.Left | Keys.Shift: - sci.CharLeftExtend(); - return false; - case Keys.Right | Keys.Shift: - sci.CharRightExtend(); - return false; + case Keys.PageDown | Keys.Shift: + case Keys.PageUp | Keys.Shift: + Hide(); + break; - case Keys.Right: - if (!CompletionList.Active) - { - sci.CharRight(); - currentPos = sci.CurrentPos; - if (sci.CurrentLine != currentLine) Hide(); - else if (OnUpdateCallTip != null) OnUpdateCallTip(sci, currentPos); - } + case Keys.Escape: + Hide(); return true; + } + + return false; + } + + private void HandlePostedKeys(Keys key) + { + switch (key) + { + case Keys.Right: + case Keys.Right | Keys.Shift: + case Keys.Right | Keys.Control: + currentPos = owner.CurrentPos; + if (OnUpdateCallTip != null) OnUpdateCallTip(owner.Owner, currentPos); + break; case Keys.Left: - if (!CompletionList.Active) + case Keys.Left | Keys.Shift: + case Keys.Left | Keys.Control: + currentPos = owner.CurrentPos; + if (OnUpdateCallTip != null) OnUpdateCallTip(owner.Owner, currentPos); + else if (currentPos < startPos) Hide(); + break; + + case Keys.Up: + case Keys.Down: + currentPos = owner.CurrentPos; + if (!completionList.Active) { - sci.CharLeft(); - currentPos = sci.CurrentPos; - if (currentPos < startPos) Hide(); - else - { - if (sci.CurrentLine != currentLine) Hide(); - else if (OnUpdateCallTip != null) OnUpdateCallTip(sci, currentPos); - } + if (OnUpdateCallTip != null) OnUpdateCallTip(owner.Owner, currentPos); + else if (currentPos < startPos) Hide(); } - return true; + break; - case Keys.Back: - sci.DeleteBack(); - currentPos = sci.CurrentPos; - if (currentPos + deltaPos < startPos) Hide(); - else if (OnUpdateCallTip != null) OnUpdateCallTip(sci, currentPos); - return true; + case Keys.PageDown: + case Keys.PageUp: + if (!completionList.Active) + Hide(); + break; - case Keys.Tab: - case Keys.Space: - return false; + case Keys.Up | Keys.Shift: + case Keys.Down | Keys.Shift: + case Keys.PageDown | Keys.Shift: + case Keys.PageUp | Keys.Shift: + Hide(); + break; - default: - if (!CompletionList.Active) Hide(); - return false; + case Keys.Back: + case Keys.Back | Keys.Control: + case Keys.Delete: + case Keys.Delete | Keys.Control: + currentPos = owner.CurrentPos; + if (OnUpdateCallTip != null) OnUpdateCallTip.BeginInvoke(owner.Owner, currentPos, null, null); + else if (currentPos < startPos) Hide(); + break; + + case Keys.Escape: + Hide(); + break; } } #endregion #region Controls fading on Control key - private static bool faded; internal void FadeOut() { - if (faded) return; - faded = true; - //base.Hide(); - toolTip.Visible = false; + if (host.Opacity != 1) return; + host.Opacity = 0; } internal void FadeIn() { - if (!faded) return; - faded = false; - //base.Show(); - toolTip.Visible = true; + if (host.Opacity == 1) return; + host.Opacity = 1; } #endregion } diff --git a/PluginCore/PluginCore/Controls/RichToolTip.cs b/PluginCore/PluginCore/Controls/RichToolTip.cs index 1e10831ecb..cc9095005d 100644 --- a/PluginCore/PluginCore/Controls/RichToolTip.cs +++ b/PluginCore/PluginCore/Controls/RichToolTip.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using PluginCore.BBCode; using PluginCore.Managers; -using ScintillaNet; using PluginCore.Helpers; namespace PluginCore.Controls @@ -14,66 +14,104 @@ namespace PluginCore.Controls /// public class RichToolTip : IEventHandler { - public delegate void UpdateTipHandler(ScintillaControl sender, Point mousePosition); + public delegate void UpdateTipHandler(Control sender, Point mousePosition); // events public event UpdateTipHandler OnUpdateSimpleTip; + public event CancelEventHandler OnShowing; + public event EventHandler OnHidden; // controls + protected InactiveForm host; protected Panel toolTip; - protected RichTextBox toolTipRTB; + protected SelectableRichTextBox toolTipRTB; protected string rawText; - protected string lastRawText; - protected string cachedRtf; protected Dictionary rtfCache; protected List rtfCacheList; protected Point mousePos; + protected ICompletionListHost owner; // We could just use Control here, or pass a reference on each related call, as Control may be a problem with default implementation + #region Public Properties - - public bool Visible + + public bool Focused + { + get { return toolTipRTB.Focused; } + } + + public bool Visible { - get { return toolTip.Visible; } + get { return host.Visible; } } public Size Size { - get { return toolTip.Size; } - set { toolTip.Size = value; } + get { return host.Size; } + set { host.Size = value; } } public Point Location { - get { return toolTip.Location; } - set { toolTip.Location = value; } + get { return host.Location; } + set { host.Location = value; } + } + + public string RawText + { + get { return rawText; } + set + { + SetText(value, true); + } + } + + public bool Selectable + { + get { return toolTipRTB.Selectable; } + set + { + toolTipRTB.Selectable = value; + } } - public string Text + public string Text { get { return toolTipRTB.Text; } - set + set { SetText(value, true); } } - + #endregion - + #region Control creation - - public RichToolTip(IMainForm mainForm) + + public RichToolTip(ICompletionListHost owner) { EventManager.AddEventHandler(this, EventType.ApplyTheme); + + // host + host = new InactiveForm(); + host.FormBorderStyle = FormBorderStyle.None; + host.ShowInTaskbar = false; + host.TopMost = true; + host.StartPosition = FormStartPosition.Manual; + host.KeyPreview = true; + host.KeyDown += Host_KeyDown; + + this.owner = owner; + // panel toolTip = new Panel(); - toolTip.Location = new Point(0,0); + toolTip.Location = new Point(0, 0); toolTip.BackColor = SystemColors.Info; toolTip.ForeColor = SystemColors.InfoText; toolTip.BorderStyle = BorderStyle.FixedSingle; - toolTip.Visible = false; - (mainForm as Form).Controls.Add(toolTip); + toolTip.Dock = DockStyle.Fill; + host.Controls.Add(toolTip); // text - toolTipRTB = new RichTextBox(); + toolTipRTB = new SelectableRichTextBox(); toolTipRTB.Font = PluginBase.Settings.DefaultFont; toolTipRTB.BackColor = SystemColors.Info; toolTipRTB.ForeColor = SystemColors.InfoText; @@ -84,14 +122,14 @@ public RichToolTip(IMainForm mainForm) toolTipRTB.WordWrap = false; toolTipRTB.Visible = true; toolTipRTB.Text = ""; + toolTipRTB.LostFocus += Host_LostFocus; toolTip.Controls.Add(toolTipRTB); // rtf cache rtfCache = new Dictionary(); rtfCacheList = new List(); } - - + public void HandleEvent(Object sender, NotifyEvent e, HandlingPriority priority) { if (e.Type == EventType.ApplyTheme) @@ -106,7 +144,23 @@ public void HandleEvent(Object sender, NotifyEvent e, HandlingPriority priority) } #endregion - + + #region Event Handlers + + protected virtual void Host_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyData == Keys.Escape) + Hide(); + } + + protected virtual void Host_LostFocus(object sender, EventArgs e) + { + if (!owner.Owner.ContainsFocus) + Hide(); + } + + #endregion + #region Tip Methods public bool AutoSize() @@ -131,10 +185,11 @@ public bool AutoSize(int availableWidth, int maxWidth) maxWidth = ScaleHelper.Scale(maxWidth); // tooltip larger than the window: wrap - int limitLeft = ((Form)PluginBase.MainForm).ClientRectangle.Left + mediumPadding; - int limitRight = ((Form)PluginBase.MainForm).ClientRectangle.Right - mediumPadding; - int limitBottom = ((Form)PluginBase.MainForm).ClientRectangle.Bottom - ScaleHelper.Scale(26); - + var screenArea = Screen.FromControl(owner.Owner).WorkingArea; + int limitLeft = screenArea.Left + smallOffsetH; + int limitRight = screenArea.Right - smallOffsetH; + int limitBottom = screenArea.Bottom - ScaleHelper.Scale(26); + // int maxW = availableWidth > 0 ? availableWidth : limitRight - limitLeft; if (maxW > maxWidth && maxWidth > 0) maxW = maxWidth; @@ -157,10 +212,10 @@ public bool AutoSize(int availableWidth, int maxWidth) int h = txtSize.Height + smallOffsetH * 2; int dh = smallOffsetH; int dw = smallOffsetW; - if (h > (limitBottom - toolTip.Top)) + if (h > (limitBottom - host.Top)) { w += ScaleHelper.Scale(15); - h = limitBottom - toolTip.Top; + h = limitBottom - host.Top; dh = smallPadding; dw = smallPadding + smallOffsetW / 2; @@ -169,13 +224,13 @@ public bool AutoSize(int availableWidth, int maxWidth) toolTipRTB.Location = new Point(smallOffsetW, smallOffsetH); toolTipRTB.Size = new Size(w, h); - toolTip.Size = new Size(w + dw, h + dh); + host.Size = new Size(w + dw, h + dh); - if (toolTip.Left < limitLeft) - toolTip.Left = limitLeft; + if (host.Left < limitLeft) + host.Left = limitLeft; - if (toolTip.Left + toolTip.Width > limitRight) - toolTip.Left = limitRight - toolTip.Width; + if (host.Left + host.Width > limitRight) + host.Left = limitRight - host.Width; if (toolTipRTB.WordWrap != wordWrap) toolTipRTB.WordWrap = wordWrap; @@ -187,49 +242,66 @@ public void ShowAtMouseLocation(string text) { if (text != Text) { - toolTip.Visible = false; + host.Visible = false; Text = text; } ShowAtMouseLocation(); } - + public void ShowAtMouseLocation() { - mousePos = ((Form)PluginBase.MainForm).PointToClient(Control.MousePosition); - toolTip.Left = mousePos.X; - if (toolTip.Right > ((Form)PluginBase.MainForm).ClientRectangle.Right) + mousePos = Control.MousePosition; + host.Left = mousePos.X;// +sci.Left; + var screen = Screen.FromPoint(mousePos); + if (host.Right > screen.WorkingArea.Right) { - toolTip.Left -= (toolTip.Right - ((Form)PluginBase.MainForm).ClientRectangle.Right); + host.Left -= (host.Right - screen.WorkingArea.Right); } - toolTip.Top = mousePos.Y - toolTip.Height - ScaleHelper.Scale(10); - toolTip.Show(); - toolTip.BringToFront(); + host.Top = mousePos.Y - host.Height - ScaleHelper.Scale(10);// +sci.Top; + if (host.Top < 5) + host.Top = mousePos.Y + 10; + Show(); } - public void UpdateTip(ScintillaControl sci) + public virtual void UpdateTip() { - if (OnUpdateSimpleTip != null) OnUpdateSimpleTip(sci, mousePos); + if (OnUpdateSimpleTip != null) OnUpdateSimpleTip(owner.Owner, mousePos); } - + public virtual void Hide() { - if (toolTip.Visible) + if (host.Visible) { - toolTip.Visible = false; + host.Visible = false; toolTipRTB.ResetText(); + if (OnHidden != null) OnHidden(this, EventArgs.Empty); } } public virtual void Show() { - toolTip.Visible = true; - toolTip.BringToFront(); + if (!host.Visible) + { + if (OnShowing != null) + { + var cancelArgs = new CancelEventArgs(); + OnShowing(this, cancelArgs); + if (cancelArgs.Cancel) + { + Hide(); + return; + } + } + + // Not really needed to set an owner, it has some advantages currently unused + host.Owner = null; // To avoid circular references that may happen because of Floating -> Docking panels + host.Show(owner.Owner); + } } public void SetText(String rawText, bool redraw) { this.rawText = rawText ?? ""; - if (redraw) Redraw(); } @@ -255,15 +327,15 @@ public void Redraw(bool autoSize) protected String getRtfFor(String bbcodeText) { - if (rtfCache.ContainsKey(bbcodeText)) - return rtfCache[bbcodeText]; + String rtfText; + + if (rtfCache.TryGetValue(bbcodeText, out rtfText)) + return rtfText; if (rtfCacheList.Count >= 512) { String key = rtfCacheList[0]; - rtfCache[key] = null; rtfCache.Remove(key); - rtfCacheList[0] = null; rtfCacheList.RemoveAt(0); } @@ -272,8 +344,78 @@ protected String getRtfFor(String bbcodeText) toolTipRTB.WordWrap = false; rtfCacheList.Add(bbcodeText); - rtfCache[bbcodeText] = BBCodeUtils.bbCodeToRtf(bbcodeText, toolTipRTB); - return rtfCache[bbcodeText]; + rtfText = BBCodeUtils.bbCodeToRtf(bbcodeText, toolTipRTB); + rtfCache[bbcodeText] = rtfText; + return rtfText; + } + + public bool IsMouseInside() + { + return host.Bounds.Contains(Control.MousePosition); + } + + #endregion + + #region Selectable RichTextBox + + // If for some reason this is not compatible with CrossOver or we want some crossplatform alternative we could place a disabled Form with Opacity to 0.009 or something like that over the control's ClientRectangle + // The downside is that on standard Windows configuration it will play an annoying "Bong" sound when clicking. Another option would be to hide the control and draw it on the form, the problem is the scrollbar, + // but we could use the ones from Form or some Panel and draw the whole text instead of just the original visible area. + protected class SelectableRichTextBox : RichTextBox + { + + private bool _selectable = true; + public bool Selectable + { + get { return _selectable; } + set + { + if (_selectable == value) return; + _selectable = value; + if (_lastCursor == null || _lastCursor == DefaultCursor) + base.Cursor = !_selectable ? Cursors.Default : DefaultCursor; + } + } + + private Cursor _lastCursor; + public override Cursor Cursor + { + get + { + return base.Cursor; + } + set + { + _lastCursor = value; + base.Cursor = value; + } + } + + protected override void DefWndProc(ref Message m) + { + const int WM_MOUSEACTIVATE = 0x21; + const int WM_CONTEXTMENU = 0x7b; + const int WM_LBUTTONDOWN = 0x201; + const int WM_LBUTTONDBLCLK = 0x203; + const int MA_NOACTIVATE = 0x0003; + + if (!_selectable) + { + switch (m.Msg) + { + case WM_MOUSEACTIVATE: + m.Result = (IntPtr)MA_NOACTIVATE; + return; + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_CONTEXTMENU: + m.Result = IntPtr.Zero; + return; + } + } + base.DefWndProc(ref m); + } + } #endregion diff --git a/PluginCore/PluginCore/Controls/UITools.cs b/PluginCore/PluginCore/Controls/UITools.cs index 70dfe471b8..142fab1f87 100644 --- a/PluginCore/PluginCore/Controls/UITools.cs +++ b/PluginCore/PluginCore/Controls/UITools.cs @@ -39,6 +39,11 @@ static public MethodCallTip CallTip get { return manager.callTip; } } + static public CompletionListControl CompletionList + { + get { return Controls.CompletionList.completionList; } + } + static public void Init() { if (manager == null) @@ -91,9 +96,9 @@ private UITools() // try { - CompletionList.CreateControl(PluginBase.MainForm); - simpleTip = new RichToolTip(PluginBase.MainForm); - callTip = new MethodCallTip(PluginBase.MainForm); + Controls.CompletionList.CreateControl(PluginBase.MainForm); + simpleTip = CompletionList.Tip; + callTip = CompletionList.CallTip; } catch(Exception ex) { @@ -104,7 +109,6 @@ private UITools() // PluginBase.MainForm.IgnoredKeys.Add(Keys.Space | Keys.Control); // complete member PluginBase.MainForm.IgnoredKeys.Add(Keys.Space | Keys.Control | Keys.Shift); // complete method - PluginBase.MainForm.DockPanel.ActivePaneChanged += new EventHandler(DockPanel_ActivePaneChanged); EventManager.AddEventHandler(this, eventMask); } #endregion @@ -114,15 +118,6 @@ private UITools() #region SciControls & MainForm Events - private void DockPanel_ActivePaneChanged(object sender, EventArgs e) - { - if (PluginBase.MainForm.DockPanel.ActivePane != null - && PluginBase.MainForm.DockPanel.ActivePane != PluginBase.MainForm.DockPanel.ActiveDocumentPane) - { - OnUIRefresh(null); - } - } - public void HandleEvent(object sender, NotifyEvent e, HandlingPriority priority) { switch (e.Type) @@ -165,6 +160,7 @@ public void ListenTo(ScintillaControl sci) sci.UpdateUI += new UpdateUIHandler(OnUIRefresh); sci.TextInserted += new TextInsertedHandler(OnTextInserted); sci.TextDeleted += new TextDeletedHandler(OnTextDeleted); + sci.GotFocus += OnGotFocus; } /// @@ -191,6 +187,7 @@ private void HandleDwellStart(ScintillaControl sci, int position) if (!bounds.Contains(mousePos)) return; // check no panel is over the editor + // 2015/02 CompletionList changes: Is this check currently needed? If a panel is over the editor HandleDwellStart doesn't seem to fire DockPanel panel = PluginBase.MainForm.DockPanel; DockContentCollection panels = panel.Contents; foreach (DockContent content in panels) @@ -228,7 +225,10 @@ private Point GetMousePosIn(Control ctrl) private void HandleDwellEnd(ScintillaControl sci, int position) { - simpleTip.Hide(); + // NOTE: simpleTip should only be hidden if a certain movement threshold is exceeded (x <> current word, y <> word + tip pos & height) or HandleDwellStart is fired again + // This would allow the user to select the tip text + if (!CompletionList.Active) + simpleTip.Hide(); if (OnMouseHoverEnd != null) OnMouseHoverEnd(sci, position); } @@ -238,16 +238,14 @@ private void HandleDwellEnd(ScintillaControl sci, int position) public bool PreFilterMessage(ref Message m) { + if (Tip.Focused || CallTip.Focused) return false; + if (m.Msg == Win32.WM_MOUSEWHEEL) // capture all MouseWheel events { - if (!callTip.CallTipActive || !callTip.Focused) + if (Win32.ShouldUseWin32()) { - if (Win32.ShouldUseWin32()) - { - Win32.SendMessage(CompletionList.GetHandle(), m.Msg, (Int32)m.WParam, (Int32)m.LParam); - return true; - } - else return false; + Win32.SendMessage(CompletionList.GetHandle(), m.Msg, (Int32)m.WParam, (Int32)m.LParam); + return true; } else return false; } @@ -256,7 +254,7 @@ public bool PreFilterMessage(ref Message m) if ((int)m.WParam == 17) // Ctrl { if (CompletionList.Active) CompletionList.FadeOut(); - if (callTip.CallTipActive && !callTip.Focused) callTip.FadeOut(); + if (callTip.CallTipActive) callTip.FadeOut(); } } else if (m.Msg == Win32.WM_KEYUP) @@ -307,8 +305,8 @@ private void OnUIRefresh(ScintillaControl sci) if (CompletionList.Active && CompletionList.CheckPosition(position)) return; if (callTip.CallTipActive && callTip.CheckPosition(position)) return; } - callTip.Hide(); CompletionList.Hide(); + callTip.Hide(); simpleTip.Hide(); } @@ -323,6 +321,15 @@ private void OnTextDeleted(ScintillaControl sci, int position, int length, int l OnTextChanged(sci, position, -length, linesAdded); } + private void OnGotFocus(object sender, EventArgs e) + { + var sci = (ScintillaControl)sender; + ((CompletionList.ScintillaHost)CompletionList.Host).SciControl = sci; + var language = ScintillaControl.Configuration.GetLanguage(sci.ConfigurationLanguage); + if (language != null) // Should we provide some custom string otherwise? + CompletionList.CharacterClass = language.characterclass.Characters; + } + private void OnChar(ScintillaControl sci, int value) { if (sci == null || DisableEvents) return; @@ -331,24 +338,23 @@ private void OnChar(ScintillaControl sci, int value) SendChar(sci, value); return; } - if (lockedSciControl != null && lockedSciControl.IsAlive) sci = (ScintillaControl)lockedSciControl.Target; - else - { - callTip.Hide(); - CompletionList.Hide(); - SendChar(sci, value); - return; - } + //if (lockedSciControl != null && lockedSciControl.IsAlive) sci = (ScintillaControl)lockedSciControl.Target; + //else + //{ + // callTip.Hide(); + // CompletionList.Hide(); + // SendChar(sci, value); + // return; + //} - if (callTip.CallTipActive) callTip.OnChar(sci, value); - if (CompletionList.Active) CompletionList.OnChar(sci, value); + if (callTip.CallTipActive) callTip.OnChar(value); + if (CompletionList.Active) Controls.CompletionList.OnChar(sci, value); else SendChar(sci, value); - return; } public void SendChar(ScintillaControl sci, int value) { - if (OnCharAdded != null) OnCharAdded(sci, value); + if (OnCharAdded != null) OnCharAdded(sci, value); } private bool HandleKeys(Keys key) @@ -359,76 +365,30 @@ private bool HandleKeys(Keys key) // list/tip shortcut dispatching if ((key == (Keys.Control | Keys.Space)) || (key == (Keys.Shift | Keys.Control | Keys.Space))) { - /*if (CompletionList.Active || callTip.CallTipActive) - { - UnlockControl(); - CompletionList.Hide(); - callTip.Hide(); - }*/ - // offer to handle the shortcut ignoreKeys = true; KeyEvent ke = new KeyEvent(EventType.Keys, key); EventManager.DispatchEvent(this, ke); ignoreKeys = false; // if not handled - show snippets - if (!ke.Handled && PluginBase.MainForm.CurrentDocument.IsEditable + if (!ke.Handled && PluginBase.MainForm.CurrentDocument.IsEditable && PluginBase.MainForm.CurrentDocument.SciControl.ContainsFocus && !PluginBase.MainForm.CurrentDocument.SciControl.IsSelectionRectangle) { PluginBase.MainForm.CallCommand("InsertSnippet", "null"); + ke.Handled = true; } - return true; + + return ke.Handled; } // toggle "long-description" for the hover tooltip - if (key == Keys.F1 && Tip.Visible && !CompletionList.Active) - { - showDetails = !showDetails; - simpleTip.UpdateTip(PluginBase.MainForm.CurrentDocument.SciControl); - return true; - } - - // are we currently displaying something? - if (!CompletionList.Active && !callTip.CallTipActive) return false; - - // hide if pressing Esc or Ctrl+Key combination - if (lockedSciControl == null || !lockedSciControl.IsAlive || key == Keys.Escape - || ((Control.ModifierKeys & Keys.Control) != 0 && Control.ModifierKeys != (Keys.Control|Keys.Alt)) ) - { - if (key == (Keys.Control | Keys.C) || key == (Keys.Control | Keys.A)) - return false; // let text copy in tip - UnlockControl(); - CompletionList.Hide((char)27); - callTip.Hide(); - return false; - } - ScintillaControl sci = (ScintillaControl)lockedSciControl.Target; - // chars - string ks = key.ToString(); - if (ks.Length == 1 || (ks.EndsWithOrdinal(", Shift") && ks.IndexOf(',') == 1) || ks.StartsWithOrdinal("NumPad")) - { - return false; - } - - // toggle "long-description" - if (key == Keys.F1) + if (key == Keys.F1 && Tip.Visible && !CompletionList.Active && !CallTip.Visible) { showDetails = !showDetails; - if (callTip.CallTipActive) callTip.UpdateTip(sci); - else CompletionList.UpdateTip(null, null); + simpleTip.UpdateTip(); return true; } - - // switches - else if ((key & Keys.ShiftKey) == Keys.ShiftKey || (key & Keys.ControlKey) == Keys.ControlKey || (key & Keys.Menu) == Keys.Menu) - { - return false; - } - // handle special keys - bool handled = false; - if (callTip.CallTipActive) handled |= callTip.HandleKeys(sci, key); - if (CompletionList.Active) handled |= CompletionList.HandleKeys(sci, key); - return handled; + return false; } diff --git a/PluginCore/ScintillaNet/Events.cs b/PluginCore/ScintillaNet/Events.cs index cf4c9b1467..4865a67496 100644 --- a/PluginCore/ScintillaNet/Events.cs +++ b/PluginCore/ScintillaNet/Events.cs @@ -2,7 +2,6 @@ namespace ScintillaNet { - public delegate void FocusHandler(ScintillaControl sender); public delegate void ZoomHandler(ScintillaControl sender); public delegate void PaintedHandler(ScintillaControl sender); public delegate void UpdateUIHandler(ScintillaControl sender); diff --git a/PluginCore/ScintillaNet/ScintillaControl.cs b/PluginCore/ScintillaNet/ScintillaControl.cs index 69bd2c0f61..ecf08533b4 100644 --- a/PluginCore/ScintillaNet/ScintillaControl.cs +++ b/PluginCore/ScintillaNet/ScintillaControl.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -21,10 +20,10 @@ namespace ScintillaNet public class ScintillaControl : Control, IEventHandler { private bool saveBOM; + private bool camelHumps; private Encoding encoding; private IntPtr directPointer; private Perform _sciFunction; - private IntPtr hwndScintilla; private bool hasHighlights = false; private bool ignoreAllKeys = false; private bool isBraceMatching = true; @@ -35,7 +34,8 @@ public class ScintillaControl : Control, IEventHandler private static Dictionary shortcutOverrides = new Dictionary(); private Enums.IndentView indentView = Enums.IndentView.Real; private Enums.SmartIndent smartIndent = Enums.SmartIndent.CPP; - private Hashtable ignoredKeys = new Hashtable(); + private HashSet ignoredKeys = new HashSet(); + private Dictionary keyCommands = new Dictionary(); private string configLanguage = String.Empty; private string fileName = String.Empty; private int lastSelectionLength = 0; @@ -194,8 +194,8 @@ private void AddScrollBars(ScintillaControl sender) Boolean hScroll = sender.IsHScrollBar; sender.IsVScrollBar = false; // Hide builtin sender.IsHScrollBar = false; // Hide builtin - sender.vScrollBar.VisibleChanged += OnResize; - sender.hScrollBar.VisibleChanged += OnResize; + sender.vScrollBar.VisibleChanged += OnResize2; + sender.hScrollBar.VisibleChanged += OnResize2; sender.vScrollBar.Scroll += sender.OnScrollBarScroll; sender.hScrollBar.Scroll += sender.OnScrollBarScroll; sender.Controls.Add(sender.hScrollBar); @@ -203,7 +203,7 @@ private void AddScrollBars(ScintillaControl sender) sender.Painted += sender.OnScrollUpdate; sender.IsVScrollBar = vScroll; sender.IsHScrollBar = hScroll; - sender.OnResize(null, null); + sender.OnResize2(null, null); } /// @@ -213,8 +213,8 @@ private void RemoveScrollBars(ScintillaControl sender) { Boolean vScroll = sender.IsVScrollBar; Boolean hScroll = sender.IsHScrollBar; - sender.vScrollBar.VisibleChanged -= OnResize; - sender.hScrollBar.VisibleChanged -= OnResize; + sender.vScrollBar.VisibleChanged -= OnResize2; + sender.hScrollBar.VisibleChanged -= OnResize2; sender.vScrollBar.Scroll -= sender.OnScrollBarScroll; sender.hScrollBar.Scroll -= sender.OnScrollBarScroll; sender.Controls.Remove(sender.hScrollBar); @@ -222,7 +222,7 @@ private void RemoveScrollBars(ScintillaControl sender) sender.Painted -= sender.OnScrollUpdate; sender.IsVScrollBar = vScroll; sender.IsHScrollBar = hScroll; - sender.OnResize(null, null); + sender.OnResize2(null, null); } #endregion @@ -238,11 +238,19 @@ public ScintillaControl(string fullpath) { try { + // We don't want .NET to use GetWindowText because we manage ('cache') our own text + SetStyle(ControlStyles.CacheText, true); + + // Necessary control styles (see TextBoxBase) + SetStyle(ControlStyles.StandardClick + | ControlStyles.StandardDoubleClick + | ControlStyles.UseTextForAccessibility + | ControlStyles.UserPaint, + false); + if (Win32.ShouldUseWin32()) { IntPtr lib = LoadLibrary(fullpath); - hwndScintilla = CreateWindowEx(0, "Scintilla", "", WS_CHILD_VISIBLE_TABSTOP, 0, 0, this.Width, this.Height, this.Handle, 0, new IntPtr(0), null); - directPointer = (IntPtr)SlowPerform(2185, 0, 0); IntPtr sciFunctionPointer = GetProcAddress(new HandleRef(null, lib), "Scintilla_DirectFunction"); if (sciFunctionPointer == IntPtr.Zero) sciFunctionPointer = GetProcAddress(new HandleRef(null, lib), "_Scintilla_DirectFunction@16"); if (sciFunctionPointer == IntPtr.Zero) @@ -251,14 +259,31 @@ public ScintillaControl(string fullpath) throw new Win32Exception(msg, new Win32Exception(Marshal.GetLastWin32Error())); } _sciFunction = (Perform)Marshal.GetDelegateForFunctionPointer(sciFunctionPointer, typeof(Perform)); - directPointer = DirectPointer; } + // Most Windows Forms controls delay-load everything until a handle is created. + // That's a major pain so we just explicity create a handle right away. + CreateControl(); + + // Clear some default shortcuts, we are interested in managing them ourselves + // IMHO a better approach would be to call ClearAllCmdKeys and set managed replacements, like current ScintillaNet + ClearCmdKey(SCK_DOWN + (SCMOD_CTRL << 16)); + ClearCmdKey(SCK_UP + (SCMOD_CTRL << 16)); + ClearCmdKey(SCK_LEFT + (SCMOD_CTRL << 16)); + ClearCmdKey(SCK_RIGHT + (SCMOD_CTRL << 16)); + ClearCmdKey(SCK_LEFT + (SCMOD_CTRL << 16) + (SCMOD_SHIFT << 16)); + ClearCmdKey(SCK_RIGHT + (SCMOD_CTRL << 16) + (SCMOD_SHIFT << 16)); + ClearCmdKey(SCK_BACK + (SCMOD_CTRL << 16)); + ClearCmdKey(SCK_DELETE + (SCMOD_CTRL << 16)); + + keyCommands[Keys.Control | Keys.Down] = LineScrollDown; + keyCommands[Keys.Control | Keys.Up] = LineScrollUp; + CamelHumps = false; + UpdateUI += new UpdateUIHandler(OnUpdateUI); UpdateUI += new UpdateUIHandler(OnBraceMatch); UpdateUI += new UpdateUIHandler(OnCancelHighlight); DoubleClick += new DoubleClickHandler(OnBlockSelect); CharAdded += new CharAddedHandler(OnSmartIndent); - Resize += new EventHandler(OnResize); this.InitScrollBars(this); } catch (Exception ex) @@ -274,11 +299,23 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public void OnResize(object sender, EventArgs e) + protected void OnResize2(object sender, EventArgs e) { Int32 vsbWidth = this.Controls.Contains(this.vScrollBar) && this.vScrollBar.Visible ? this.vScrollBar.Width : 0; Int32 hsbHeight = this.Controls.Contains(this.hScrollBar) && this.hScrollBar.Visible ? this.hScrollBar.Height : 0; - if (Win32.ShouldUseWin32()) SetWindowPos(this.hwndScintilla, 0, ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - vsbWidth, ClientRectangle.Height - hsbHeight, 0); + //if (Win32.ShouldUseWin32()) SetWindowPos(this.hwndScintilla, 0, ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - vsbWidth, ClientRectangle.Height - hsbHeight, 0); + } + + protected override CreateParams CreateParams + { + get + { + // Per Scintilla documentation, the Window Class name... + CreateParams cp = base.CreateParams; + cp.ClassName = "Scintilla"; + + return cp; + } } #endregion @@ -287,7 +324,6 @@ public void OnResize(object sender, EventArgs e) public event KeyHandler Key; public event ZoomHandler Zoom; - public event FocusHandler FocusChanged; public event StyleNeededHandler StyleNeeded; public event CharAddedHandler CharAdded; public event SavePointReachedHandler SavePointReached; @@ -326,19 +362,13 @@ public void OnResize(object sender, EventArgs e) public event AutoCCharDeletedHandler AutoCCharDeleted; public event UpdateSyncHandler UpdateSync; public event SelectionChangedHandler SelectionChanged; + public event ScrollEventHandler Scroll; + public event KeyEventHandler KeyPosted; //Hacky event for MethodCallTip, although with some rather valid use cases #endregion #region Scintilla Properties - /// - /// Gets the sci handle - /// - public IntPtr HandleSci - { - get { return hwndScintilla; } - } - /// /// Current used configuration /// @@ -1774,7 +1804,9 @@ public IntPtr DirectPointer { get { - return (IntPtr)SPerform(2185, 0, 0); + if (directPointer == IntPtr.Zero) + directPointer = SendMessage(Handle, 2185, 0, 0); + return directPointer; } } @@ -2422,7 +2454,7 @@ public bool ScrollWidthTracking /// public virtual void AddIgnoredKeys(Keys keys) { - ignoredKeys.Add((int)keys, (int)keys); + ignoredKeys.Add((int)keys); } /// @@ -2446,15 +2478,7 @@ public virtual void ClearIgnoredKeys() /// public virtual bool ContainsIgnoredKeys(Keys keys) { - return ignoredKeys.ContainsKey((int)keys); - } - - /// - /// Sets the focus to the control - /// - public new bool Focus() - { - return SetFocus(hwndScintilla) != IntPtr.Zero; + return ignoredKeys.Contains((int)keys); } /// @@ -4220,6 +4244,28 @@ public void DelWordRight() SPerform(2336, 0, 0); } + /// + /// Delete the word part to the left of the caret. + /// + public void DelWordPartLeft() + { + int currPos = CurrentPos; + SetSel(currPos, currPos); + WordPartLeftExtend(); + Clear(); + } + + /// + /// Delete the word part to the right of the caret. + /// + public void DelWordPartRight() + { + int currPos = CurrentPos; + SetSel(currPos, currPos); + WordPartRightExtend(); + Clear(); + } + /// /// Cut the line containing the caret. /// @@ -4273,7 +4319,16 @@ public void UpperCase() /// public void LineScrollDown() { + int oldScroll = FirstVisibleLine; + SPerform(2342, 0, 0); + + int newScroll = FirstVisibleLine; + + if (newScroll == oldScroll) return; + + // Decrement? + OnScroll(new ScrollEventArgs(ScrollEventType.SmallIncrement, oldScroll, newScroll, ScrollOrientation.VerticalScroll)); } /// @@ -4281,7 +4336,16 @@ public void LineScrollDown() /// public void LineScrollUp() { + int oldScroll = FirstVisibleLine; + SPerform(2343, 0, 0); + + int newScroll = FirstVisibleLine; + + if (newScroll == oldScroll) return; + + // Decrement? + OnScroll(new ScrollEventArgs(ScrollEventType.SmallIncrement, oldScroll, newScroll, ScrollOrientation.VerticalScroll)); } /// @@ -5173,10 +5237,16 @@ public int ContractedFoldNext(int lineStart) public const int MAXDWELLTIME = 10000000; private const int WM_NOTIFY = 0x004e; + private const int WM_USER = 0x0400; + private const int WM_REFLECT = WM_USER + 0x1C00; private const int WM_SYSCHAR = 0x106; private const int WM_COMMAND = 0x0111; private const int WM_KEYDOWN = 0x0100; + private const int WM_SETCURSOR = 0x0020; + private const int WM_MOUSEWHEEL = 0x20A; private const int WM_SYSKEYDOWN = 0x0104; + private const int WM_HSCROLL = 0x114; + private const int WM_VSCROLL = 0x115; private const int WM_DROPFILES = 0x0233; private const uint WS_CHILD = (uint)0x40000000L; private const uint WS_VISIBLE = (uint)0x10000000L; @@ -5184,6 +5254,16 @@ public int ContractedFoldNext(int lineStart) private const uint WS_CHILD_VISIBLE_TABSTOP = WS_CHILD | WS_VISIBLE | WS_TABSTOP; private const int PATH_LEN = 1024; + private const int SCK_BACK = 8; + private const int SCK_DOWN = 300; + private const int SCK_UP = 301; + private const int SCK_LEFT = 302; + private const int SCK_RIGHT = 303; + private const int SCK_DELETE = 308; + private const int SCMOD_SHIFT = 1; + private const int SCMOD_CTRL = 2; + private const int SCMOD_ALT = 4; + #endregion #region Scintilla Shortcuts @@ -5274,10 +5354,7 @@ public ShortcutOverride(Keys keys, Action action) public static extern int GetDeviceCaps(IntPtr hdc, Int32 capindex); [DllImport("user32.dll")] - public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); - - [DllImport("user32.dll")] - public static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); [DllImport("shell32.dll")] public static extern int DragQueryFileA(IntPtr hDrop, uint idx, IntPtr buff, int sz); @@ -5290,29 +5367,54 @@ public ShortcutOverride(Keys keys, Action action) public delegate IntPtr Perform(IntPtr sci, int iMessage, IntPtr wParam, IntPtr lParam); - public UInt32 SlowPerform(UInt32 message, UInt32 wParam, UInt32 lParam) - { - return (UInt32)SendMessage(hwndScintilla, message, (int)wParam, (int)lParam); - } public int SPerform(int message, int wParam, UInt32 lParam) { - if (Win32.ShouldUseWin32()) return (int)_sciFunction(directPointer, message, (IntPtr)wParam, (IntPtr)lParam); + if (Win32.ShouldUseWin32()) return (int)_sciFunction(DirectPointer, message, (IntPtr)wParam, (IntPtr)lParam); else return Encoding.ASCII.CodePage; } public int SPerform(int message, int wParam, int lParam) { - if (Win32.ShouldUseWin32()) return (int)_sciFunction(directPointer, message, (IntPtr)wParam, (IntPtr)lParam); + if (Win32.ShouldUseWin32()) return (int)_sciFunction(DirectPointer, message, (IntPtr)wParam, (IntPtr)lParam); else return Encoding.ASCII.CodePage; } public int SPerform(int message, int wParam, IntPtr lParam) { - if (Win32.ShouldUseWin32()) return (int)_sciFunction(directPointer, message, (IntPtr)wParam, lParam); + if (Win32.ShouldUseWin32()) return (int)_sciFunction(DirectPointer, message, (IntPtr)wParam, lParam); else return Encoding.ASCII.CodePage; } + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + Action keyCommand; + if (!e.Handled && keyCommands.TryGetValue(e.KeyData, out keyCommand)) + { + keyCommand(); + OnKeyPosted(e); + e.SuppressKeyPress = true; + } + } + + protected virtual void OnKeyPosted(KeyEventArgs e) + { + if (KeyPosted != null) + KeyPosted(this, e); + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + protected virtual void OnScroll(ScrollEventArgs e) + { + if (Scroll != null) + Scroll(this, e); + } + public override bool PreProcessMessage(ref Message m) { switch (m.Msg) @@ -5320,10 +5422,13 @@ public override bool PreProcessMessage(ref Message m) case WM_KEYDOWN: { Int32 keys = (Int32)Control.ModifierKeys + (Int32)m.WParam; - if (!IsFocus || ignoreAllKeys || ignoredKeys.ContainsKey(keys)) + if (!IsFocus || ignoreAllKeys || ignoredKeys.Contains(keys)) { if (this.ExecuteShortcut(keys) || base.PreProcessMessage(ref m)) return true; } + if (keys == 8) + // Hacky... Back key, it's handled by ASCompletion, before it was set as a shortcut so it was present in the ignoredKeys collection, that was in several ways hackier + return base.PreProcessMessage(ref m); if (((Control.ModifierKeys & Keys.Control) != 0) && ((Control.ModifierKeys & Keys.Alt) == 0)) { Int32 code = (Int32)m.WParam; @@ -5347,195 +5452,240 @@ public override bool PreProcessMessage(ref Message m) return false; } - protected override void WndProc(ref System.Windows.Forms.Message m) + private void WmScroll(ref Message m) { - if (m.Msg == WM_COMMAND) + ScrollOrientation so = ScrollOrientation.VerticalScroll; + int oldScroll = 0, newScroll = 0; + ScrollEventType set; + if (m.Msg == WM_HSCROLL) { - Int32 message = (m.WParam.ToInt32() >> 16) & 0xffff; - if (message == (int)Enums.Command.SetFocus || message == (int)Enums.Command.KillFocus) - { - if (FocusChanged != null) FocusChanged(this); - } + so = ScrollOrientation.HorizontalScroll; + oldScroll = XOffset; + + // Let Scintilla Handle the scroll Message to actually perform scrolling + base.WndProc(ref m); + newScroll = XOffset; } - else if (m.Msg == WM_NOTIFY) + else { - SCNotification scn = (SCNotification)Marshal.PtrToStructure(m.LParam, typeof(SCNotification)); - if (scn.nmhdr.hwndFrom == hwndScintilla && !this.DisableAllSciEvents) - { - switch (scn.nmhdr.code) + so = ScrollOrientation.VerticalScroll; + oldScroll = FirstVisibleLine; + base.WndProc(ref m); + newScroll = FirstVisibleLine; + } + + if (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL) + set = (ScrollEventType)((short)((int)(long)m.WParam & 0xffff)); + else + { + if (oldScroll == newScroll) return; + set = oldScroll > newScroll ? ScrollEventType.SmallDecrement : ScrollEventType.SmallIncrement; + } + + OnScroll(new ScrollEventArgs(set, oldScroll, newScroll, so)); + } + + protected override void DefWndProc(ref Message m) + { + base.DefWndProc(ref m); + + if (m.Msg == WM_KEYDOWN || m.Msg == WM_SYSKEYDOWN) // If we're worried about performance/GC, we can store latest OnKeyDown e + OnKeyPosted(new KeyEventArgs((Keys)((int)m.WParam) | ModifierKeys)); + } + + protected override void WndProc(ref System.Windows.Forms.Message m) + { + switch (m.Msg) + { + case WM_SETCURSOR: + base.DefWndProc(ref m); + break; + + case WM_NOTIFY + WM_REFLECT: + SCNotification scn = (SCNotification)Marshal.PtrToStructure(m.LParam, typeof(SCNotification)); + if (!this.DisableAllSciEvents) { - case (uint)Enums.ScintillaEvents.StyleNeeded: - if (StyleNeeded != null) StyleNeeded(this, scn.position); - break; + switch (scn.nmhdr.code) + { + case (uint)Enums.ScintillaEvents.StyleNeeded: + if (StyleNeeded != null) StyleNeeded(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.CharAdded: - if (CharAdded != null) CharAdded(this, scn.ch); - break; + case (uint)Enums.ScintillaEvents.CharAdded: + if (CharAdded != null) CharAdded(this, scn.ch); + break; - case (uint)Enums.ScintillaEvents.SavePointReached: - if (SavePointReached != null) SavePointReached(this); - break; + case (uint)Enums.ScintillaEvents.SavePointReached: + if (SavePointReached != null) SavePointReached(this); + break; - case (uint)Enums.ScintillaEvents.SavePointLeft: - if (SavePointLeft != null) SavePointLeft(this); - break; + case (uint)Enums.ScintillaEvents.SavePointLeft: + if (SavePointLeft != null) SavePointLeft(this); + break; - case (uint)Enums.ScintillaEvents.ModifyAttemptRO: - if (ModifyAttemptRO != null) ModifyAttemptRO(this); - break; + case (uint)Enums.ScintillaEvents.ModifyAttemptRO: + if (ModifyAttemptRO != null) ModifyAttemptRO(this); + break; - case (uint)Enums.ScintillaEvents.Key: - if (Key != null) Key(this, scn.ch, scn.modifiers); - break; + case (uint)Enums.ScintillaEvents.Key: + if (Key != null) Key(this, scn.ch, scn.modifiers); + break; - case (uint)Enums.ScintillaEvents.DoubleClick: - if (DoubleClick != null) DoubleClick(this); - break; + case (uint)Enums.ScintillaEvents.DoubleClick: + if (DoubleClick != null) DoubleClick(this); + break; - case (uint)Enums.ScintillaEvents.UpdateUI: - if (UpdateUI != null) UpdateUI(this); - break; + case (uint)Enums.ScintillaEvents.UpdateUI: + if (UpdateUI != null) UpdateUI(this); + break; - case (uint)Enums.ScintillaEvents.MacroRecord: - if (MacroRecord != null) MacroRecord(this, scn.message, scn.wParam, scn.lParam); - break; + case (uint)Enums.ScintillaEvents.MacroRecord: + if (MacroRecord != null) MacroRecord(this, scn.message, scn.wParam, scn.lParam); + break; - case (uint)Enums.ScintillaEvents.MarginClick: - if (MarginClick != null) MarginClick(this, scn.modifiers, scn.position, scn.margin); - break; + case (uint)Enums.ScintillaEvents.MarginClick: + if (MarginClick != null) MarginClick(this, scn.modifiers, scn.position, scn.margin); + break; - case (uint)Enums.ScintillaEvents.NeedShown: - if (NeedShown != null) NeedShown(this, scn.position, scn.length); - break; + case (uint)Enums.ScintillaEvents.NeedShown: + if (NeedShown != null) NeedShown(this, scn.position, scn.length); + break; - case (uint)Enums.ScintillaEvents.Painted: - if (Painted != null) Painted(this); - break; + case (uint)Enums.ScintillaEvents.Painted: + if (Painted != null) Painted(this); + break; - case (uint)Enums.ScintillaEvents.UserListSelection: - if (UserListSelection != null) UserListSelection(this, scn.listType, MarshalStr(scn.text)); - break; + case (uint)Enums.ScintillaEvents.UserListSelection: + if (UserListSelection != null) UserListSelection(this, scn.listType, MarshalStr(scn.text)); + break; - case (uint)Enums.ScintillaEvents.URIDropped: - if (URIDropped != null) URIDropped(this, MarshalStr(scn.text)); - break; + case (uint)Enums.ScintillaEvents.URIDropped: + if (URIDropped != null) URIDropped(this, MarshalStr(scn.text)); + break; - case (uint)Enums.ScintillaEvents.DwellStart: - if (DwellStart != null) DwellStart(this, scn.position); - break; + case (uint)Enums.ScintillaEvents.DwellStart: + if (DwellStart != null) DwellStart(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.DwellEnd: - if (DwellEnd != null) DwellEnd(this, scn.position); - break; + case (uint)Enums.ScintillaEvents.DwellEnd: + if (DwellEnd != null) DwellEnd(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.Zoom: - if (Zoom != null) Zoom(this); - break; + case (uint)Enums.ScintillaEvents.Zoom: + if (Zoom != null) Zoom(this); + break; - case (uint)Enums.ScintillaEvents.HotspotClick: - if (HotSpotClick != null) HotSpotClick(this, scn.modifiers, scn.position); - break; + case (uint)Enums.ScintillaEvents.HotspotClick: + if (HotSpotClick != null) HotSpotClick(this, scn.modifiers, scn.position); + break; - case (uint)Enums.ScintillaEvents.HotspotDoubleClick: - if (HotSpotDoubleClick != null) HotSpotDoubleClick(this, scn.modifiers, scn.position); - break; + case (uint)Enums.ScintillaEvents.HotspotDoubleClick: + if (HotSpotDoubleClick != null) HotSpotDoubleClick(this, scn.modifiers, scn.position); + break; - case (uint)Enums.ScintillaEvents.CalltipClick: - if (CallTipClick != null) CallTipClick(this, scn.position); - break; + case (uint)Enums.ScintillaEvents.CalltipClick: + if (CallTipClick != null) CallTipClick(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.AutoCSelection: - if (AutoCSelection != null) AutoCSelection(this, MarshalStr(scn.text)); - break; + case (uint)Enums.ScintillaEvents.AutoCSelection: + if (AutoCSelection != null) AutoCSelection(this, MarshalStr(scn.text)); + break; - case (uint)Enums.ScintillaEvents.IndicatorClick: - if (IndicatorClick != null) IndicatorClick(this, scn.position); - break; + case (uint)Enums.ScintillaEvents.IndicatorClick: + if (IndicatorClick != null) IndicatorClick(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.IndicatorRelease: - if (IndicatorRelease != null) IndicatorRelease(this, scn.position); - break; + case (uint)Enums.ScintillaEvents.IndicatorRelease: + if (IndicatorRelease != null) IndicatorRelease(this, scn.position); + break; - case (uint)Enums.ScintillaEvents.AutoCCharDeleted: - if (AutoCCharDeleted != null) AutoCCharDeleted(this); - break; + case (uint)Enums.ScintillaEvents.AutoCCharDeleted: + if (AutoCCharDeleted != null) AutoCCharDeleted(this); + break; - case (uint)Enums.ScintillaEvents.AutoCCancelled: - if (AutoCCancelled != null) AutoCCancelled(this); - break; + case (uint)Enums.ScintillaEvents.AutoCCancelled: + if (AutoCCancelled != null) AutoCCancelled(this); + break; - case (uint)Enums.ScintillaEvents.Modified: - bool notify = false; - if ((scn.modificationType & (uint)Enums.ModificationFlags.InsertText) > 0) - { - if (TextInserted != null) TextInserted(this, scn.position, scn.length, scn.linesAdded); - notify = true; - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.DeleteText) > 0) - { - if (TextDeleted != null) TextDeleted(this, scn.position, scn.length, scn.linesAdded); - notify = true; - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeStyle) > 0) - { - if (StyleChanged != null) StyleChanged(this, scn.position, scn.length); - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeFold) > 0) - { - if (FoldChanged != null) FoldChanged(this, scn.line, scn.foldLevelNow, scn.foldLevelPrev); - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.UserPerformed) > 0) - { - if (UserPerformed != null) UserPerformed(this); - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.UndoPerformed) > 0) - { - if (UndoPerformed != null) UndoPerformed(this); - notify = true; - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.RedoPerformed) > 0) - { - if (RedoPerformed != null) RedoPerformed(this); - notify = true; - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.LastStepInUndoRedo) > 0) - { - if (LastStepInUndoRedo != null) LastStepInUndoRedo(this); - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeMarker) > 0) - { - if (MarkerChanged != null) MarkerChanged(this, scn.line); - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.BeforeInsert) > 0) - { - if (BeforeInsert != null) BeforeInsert(this, scn.position, scn.length); - notify = false; - } - if ((scn.modificationType & (uint)Enums.ModificationFlags.BeforeDelete) > 0) - { - if (BeforeDelete != null) BeforeDelete(this, scn.position, scn.length); - notify = false; - } - if (notify && Modified != null && scn.text != null) - { - try + case (uint)Enums.ScintillaEvents.Modified: + bool notify = false; + if ((scn.modificationType & (uint)Enums.ModificationFlags.InsertText) > 0) { - string text = MarshalStr(scn.text, scn.length); - Modified(this, scn.position, scn.modificationType, text, scn.length, scn.linesAdded, scn.line, scn.foldLevelNow, scn.foldLevelPrev); + if (TextInserted != null) TextInserted(this, scn.position, scn.length, scn.linesAdded); + notify = true; } - catch { } - } - break; + if ((scn.modificationType & (uint)Enums.ModificationFlags.DeleteText) > 0) + { + if (TextDeleted != null) TextDeleted(this, scn.position, scn.length, scn.linesAdded); + notify = true; + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeStyle) > 0) + { + if (StyleChanged != null) StyleChanged(this, scn.position, scn.length); + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeFold) > 0) + { + if (FoldChanged != null) FoldChanged(this, scn.line, scn.foldLevelNow, scn.foldLevelPrev); + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.UserPerformed) > 0) + { + if (UserPerformed != null) UserPerformed(this); + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.UndoPerformed) > 0) + { + if (UndoPerformed != null) UndoPerformed(this); + notify = true; + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.RedoPerformed) > 0) + { + if (RedoPerformed != null) RedoPerformed(this); + notify = true; + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.LastStepInUndoRedo) > 0) + { + if (LastStepInUndoRedo != null) LastStepInUndoRedo(this); + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.ChangeMarker) > 0) + { + if (MarkerChanged != null) MarkerChanged(this, scn.line); + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.BeforeInsert) > 0) + { + if (BeforeInsert != null) BeforeInsert(this, scn.position, scn.length); + notify = false; + } + if ((scn.modificationType & (uint)Enums.ModificationFlags.BeforeDelete) > 0) + { + if (BeforeDelete != null) BeforeDelete(this, scn.position, scn.length); + notify = false; + } + if (notify && Modified != null && scn.text != null) + { + try + { + string text = MarshalStr(scn.text, scn.length); + Modified(this, scn.position, scn.modificationType, text, scn.length, scn.linesAdded, scn.line, scn.foldLevelNow, scn.foldLevelPrev); + } + catch { } + } + break; + } } - } - } - else if (m.Msg == WM_DROPFILES) - { - if (Win32.ShouldUseWin32()) HandleFileDrop(m.WParam); - } - else - { - base.WndProc(ref m); + break; + + case WM_DROPFILES: + if (Win32.ShouldUseWin32()) HandleFileDrop(m.WParam); + break; + + case WM_HSCROLL: + case WM_VSCROLL: + case WM_MOUSEWHEEL: + WmScroll(ref m); + break; + + default: + base.WndProc(ref m); + break; } } @@ -5968,6 +6118,36 @@ public bool SaveBOM } } + /// + /// Defines the current behaviour for next/previous word-related actions + /// + public bool CamelHumps + { + get { return camelHumps; } + set + { + camelHumps = value; + if (!value) + { + keyCommands[Keys.Control | Keys.Left] = WordLeft; + keyCommands[Keys.Control | Keys.Right] = WordRight; + keyCommands[Keys.Control | Keys.Shift | Keys.Left] = WordLeftExtend; + keyCommands[Keys.Control | Keys.Shift | Keys.Right] = WordRightExtend; + keyCommands[Keys.Control | Keys.Back] = DelWordLeft; + keyCommands[Keys.Control | Keys.Delete] = DelWordRight; + } + else + { + keyCommands[Keys.Control | Keys.Left] = WordPartLeftEx; + keyCommands[Keys.Control | Keys.Right] = WordPartRightEx; + keyCommands[Keys.Control | Keys.Shift | Keys.Left] = WordPartLeftExtendEx; + keyCommands[Keys.Control | Keys.Shift | Keys.Right] = WordPartRightExtendEx; + keyCommands[Keys.Control | Keys.Back] = DelWordPartLeftEx; + keyCommands[Keys.Control | Keys.Delete] = DelWordPartRightEx; + } + } + } + /// /// Adds a line end marker to the end of the document /// @@ -7131,6 +7311,318 @@ public string GetWordLeft(int position, bool skipWS) return word; } + /// + /// Delete the word part to the left of the caret. Supports Unicode and uses a slightly different ruleset + /// + public void DelWordPartLeftEx() + { + int currPos = CurrentPos; + SetSel(currPos, currPos); + WordPartLeftExtendEx(); + Clear(); + } + + /// + /// Delete the word part to the right of the caret. Supports Unicode and uses a slightly different ruleset + /// + public void DelWordPartRightEx() + { + int currPos = CurrentPos; + SetSel(currPos, currPos); + WordPartRightExtendEx(); + Clear(); + } + + /// + /// Move to the previous change in capitalisation. Supports Unicode and uses a slightly different ruleset + /// + public void WordPartLeftEx() + { + int pos = GetCustomWordPartLeft() + 1; + SetSel(pos, pos); + CharLeft(); // Hack to force caret visible, is there a better way for this? + } + + /// + /// Move to the change next in capitalisation. Supports Unicode and uses a slightly different ruleset + /// + public void WordPartRightEx() + { + int pos = GetCustomWordPartRight() - 1; + SetSel(pos, pos); + CharRight(); // Hack to force caret visible, is there a better way for this? + } + + /// + /// Move to the previous change in capitalisation extending selection + /// to new caret position. Supports Unicode and uses a slightly different ruleset + /// + public void WordPartLeftExtendEx() + { + int pos = GetCustomWordPartLeft(); + int selStart = SelectionStart; + int selEnd = SelectionEnd; + if (CurrentPos > selStart) selEnd = selStart; + SetSel(selEnd, pos); + } + + /// + /// Move to the next change in capitalisation extending selection + /// to new caret position. Supports Unicode and uses a slightly different ruleset + /// + public void WordPartRightExtendEx() + { + int pos = GetCustomWordPartRight(); + int selStart = SelectionStart; + int selEnd = SelectionEnd; + if (CurrentPos < selEnd) selStart = selEnd; + SetSel(selStart, pos); + } + + private int GetCustomWordPartLeft() + { + /* This is a more or less direct translation of default Scintilla implementation with the following differences: + * - Sadly, Scintilla doesn't support multi byte characters in the WORDPART* function, this solves it. + * - This implementation is a bit more complex, as checks for some more types of characters, making browsing a bit more fluent. + * - Line jumps are treated differently, the default implementation just skips all lines, this one behaves like normal WORD* functions, with stops in between if there are whitespaces. + * - Since we cannot use the CharAt function we have to get the properly encoded text, but since getting the whole text may use way more resources than needed, we go line per line, and + * this makes code a bit more difficult to read. + */ + int pos = CurrentPos; + if (pos == 0) return 0; + + int line = LineFromPosition(pos - 1); + int linePos = pos - PositionFromLine(line); + int i, count = 0; + char startChar = '\0'; + + do + { + int sz = SPerform(2153, line, 0); + byte[] buffer = new byte[sz + 1]; + unsafe + { + fixed (byte* b = buffer) SPerform(2153, line, (uint)b); + } + string lineText = Encoding.GetEncoding(CodePage).GetString(buffer, 0, linePos == 0 ? sz : linePos); + + i = lineText.Length - 1; + if (count == 0) + startChar = lineText[i]; + if (count == 0 && char.GetUnicodeCategory(startChar) == System.Globalization.UnicodeCategory.ConnectorPunctuation) + { + while (i > 0 && char.GetUnicodeCategory(lineText[i]) == System.Globalization.UnicodeCategory.ConnectorPunctuation) + { + --i; + } + } + if (i > 0) + { + if (count == 0) + { + startChar = lineText[i]; + --i; + } + if (char.IsLower(startChar)) + { + while (i > 0 && char.IsLower(lineText[i])) + --i; + if (!char.IsUpper(lineText[i]) && !char.IsLower(lineText[i])) + ++i; + } + else if (char.IsUpper(startChar)) + { + while (i > 0 && char.IsUpper(lineText[i])) + --i; + if (!char.IsUpper(lineText[i])) + ++i; + } + else if (char.IsLetter(startChar)) + { + while (i > 0 && char.IsLetter(lineText[i])) + --i; + if (!char.IsLetter(lineText[i])) + ++i; + } + else if (char.IsDigit(startChar)) + { + while (i > 0 && char.IsDigit(lineText[i])) + --i; + if (!char.IsDigit(lineText[i])) + ++i; + } + else if (char.IsPunctuation(startChar) || char.IsSymbol(startChar)) + { + char c; + while (i > 0 && (char.IsPunctuation((c = lineText[i])) || char.IsSymbol(c))) + --i; + c = lineText[i]; + if (!char.IsPunctuation(c) && !char.IsSymbol(c)) + ++i; + } + else if (startChar == '\n' || startChar == '\r') + { + char c; + while (i > 0 && ((c = lineText[i]) == '\n' || c == '\r')) + --i; + c = lineText[i]; + if (c != '\n' && c != '\r') + ++i; + } + else if (char.IsWhiteSpace(startChar)) + { + char c; + while (i > 0 && (c = lineText[i]) != '\n' && c != '\r' && + char.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.ConnectorPunctuation && + (char.IsPunctuation(c) || char.IsWhiteSpace(c))) + --i; + + if (i == 0) + startChar = '\n'; + else if (!char.IsWhiteSpace(lineText[i])) + ++i; + } + else if (!IsAscii(startChar)) + { + while (i > 0 && !IsAscii(lineText[i])) + --i; + if (IsAscii(lineText[i])) + ++i; + } + else + { + ++i; + } + } + else if (line > 0 && char.IsWhiteSpace(startChar)) + startChar = '\n'; + + count += Encoding.GetEncoding(CodePage).GetByteCount(lineText.ToCharArray(), i, lineText.Length - i); + linePos = 0; + line--; + } while (i == 0 && line >= 0); + + return pos - count; + } + + private int GetCustomWordPartRight() + { + /* This is a more or less direct translation of default Scintilla implementation with the following differences: + * - Sadly, Scintilla doesn't support multi byte characters in the WORDPART* function, this solves it. + * - This implementation is a bit more complex, as checks for some more types of characters, making browsing a bit more fluent. + * - Line jumps are treated differently, the default implementation just skips all lines, this one behaves like normal WORD* functions, with stops in between if there are whitespaces. + * - Since we cannot use the CharAt function we have to get the properly encoded text, but since getting the whole text may use way more resources than needed, we go line per line, and + * this makes code a bit more difficult to read. + */ + int pos = CurrentPos; + if (pos == TextLength) return pos; + int lineCount = LineCount; + int line = LineFromPosition(pos); + int linePos = pos - PositionFromLine(line); + int length, i, count = 0; + char startChar = '\0'; + + do + { + int sz = SPerform(2153, line, 0); + byte[] buffer = new byte[sz + 1]; + unsafe + { + fixed (byte* b = buffer) SPerform(2153, line, (uint)b); + } + string lineText = Encoding.GetEncoding(CodePage).GetString(buffer, linePos, sz - linePos); + + length = lineText.Length; + i = 0; + + if (count == 0) + { + startChar = lineText[i]; + if (char.GetUnicodeCategory(startChar) == System.Globalization.UnicodeCategory.ConnectorPunctuation) + { + while (i < length && char.GetUnicodeCategory(lineText[i]) == System.Globalization.UnicodeCategory.ConnectorPunctuation) + ++i; + startChar = lineText[i]; + } + } + if (char.IsLower(startChar)) + { + while (i < length && char.IsLower(lineText[i])) + ++i; + // We may be interested in not running this loop if startChar was the same + while (i < length && char.GetUnicodeCategory(lineText[i]) == System.Globalization.UnicodeCategory.ConnectorPunctuation) + ++i; + } + else if (char.IsUpper(startChar)) + { + if (i < length - 1 && char.IsLower(lineText[i + 1])) + { + ++i; + while (i < length && char.IsLower(lineText[i])) + ++i; + } + else + { + while (i < length && char.IsUpper(lineText[i])) + ++i; + } + if (i < length && char.IsLower(lineText[i]) && char.IsUpper(lineText[i - 1])) + --i; + } + else if (char.IsLetter(startChar)) + { + while (i < length && char.IsLetter(lineText[i])) + ++i; + } + else if (char.IsDigit(startChar)) + { + while (i < length && char.IsDigit(lineText[i])) + ++i; + } + else if (char.IsPunctuation(startChar) || char.IsSymbol(startChar)) + { + char c; + while (i < length && (char.IsPunctuation((c = lineText[i])) || char.IsSymbol(c))) + ++i; + } + else if (startChar == '\n' || startChar == '\r') + { + char c; + while (i < length && ((c = lineText[i]) == '\r' || c == '\n')) + ++i; + while (i < length && char.IsWhiteSpace((c = lineText[i])) && c != '\r' && c != '\n') + ++i; + } + else if (char.IsWhiteSpace(startChar)) + { + if (count == 0) ++i; + char c; + while (i < length && (c = lineText[i]) != '\r' && c != '\n' && char.IsWhiteSpace(c)) + ++i; + } + else if (!IsAscii(startChar)) + { + while (i < length && !IsAscii(lineText[i])) + ++i; + } + else + { + ++i; + count += Encoding.GetEncoding(CodePage).GetByteCount(lineText.ToCharArray(), 0, i); + break; + } + count += Encoding.GetEncoding(CodePage).GetByteCount(lineText.ToCharArray(), 0, i); + line++; + linePos = 0; + } while (i == length && line < lineCount); + return pos + count; + } + + private static bool IsAscii(char c) + { + return c < 0x80; + } + #endregion }