diff --git a/External/Plugins/ASCompletion/ASCompletion.csproj b/External/Plugins/ASCompletion/ASCompletion.csproj index 019fa58d6a..2b3e26f427 100644 --- a/External/Plugins/ASCompletion/ASCompletion.csproj +++ b/External/Plugins/ASCompletion/ASCompletion.csproj @@ -92,6 +92,7 @@ ModelsExplorer.cs + diff --git a/External/Plugins/ASCompletion/Completion/ASComplete.cs b/External/Plugins/ASCompletion/Completion/ASComplete.cs index ac8daa44bf..f34a38f7b2 100644 --- a/External/Plugins/ASCompletion/Completion/ASComplete.cs +++ b/External/Plugins/ASCompletion/Completion/ASComplete.cs @@ -178,7 +178,7 @@ static public bool OnChar(ScintillaControl Sci, int Value, bool autoHide) { return HandleColonCompletion(Sci, "", autoHide); } - else break; + break; case '<': if (features.hasGenerics && position > 2) @@ -188,7 +188,7 @@ static public bool OnChar(ScintillaControl Sci, int Value, bool autoHide) return HandleColonCompletion(Sci, "", autoHide); return false; } - else break; + break; case '(': case ',': @@ -197,7 +197,7 @@ static public bool OnChar(ScintillaControl Sci, int Value, bool autoHide) else return false; case ')': - if (UITools.CallTip.CallTipActive) UITools.CallTip.Hide(); + if (CompletionList.CallTip.CallTipActive) CompletionList.CallTip.Hide(); return false; case '*': @@ -241,7 +241,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)) @@ -257,11 +257,11 @@ 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 - else if (keys == (Keys.Control | Keys.Shift | Keys.Space)) + else if (keys == (Keys.Control | Keys.Shift | Keys.Space) && Sci.ContainsFocus) { if (ASContext.HasContext && ASContext.Context.IsFileValid) { @@ -275,7 +275,7 @@ 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); @@ -1285,9 +1285,27 @@ private static bool IsDeclaration(string line, ContextFeatures features) static private string prevParam = ""; static private string paramInfo = ""; + static private CompletionListControl completionList; + /// + /// Target Completion List to use + /// + static public CompletionListControl CompletionList + { + get + { + if (completionList == null) + completionList = UITools.CompletionList; + return completionList; + } + set + { + completionList = value; + } + } + static public bool HasCalltip() { - return UITools.CallTip.CallTipActive && (calltipDef != null); + return CompletionList.CallTip.CallTipActive && (calltipDef != null); } /// @@ -1339,17 +1357,17 @@ static private void ShowCalltip(ScintillaControl Sci, int paramIndex, bool force } // show calltip - if (!UITools.CallTip.CallTipActive || UITools.Manager.ShowDetails != calltipDetails || paramName != prevParam) + if (!CompletionList.CallTip.CallTipActive || UITools.Manager.ShowDetails != calltipDetails || paramName != prevParam) { prevParam = paramName; calltipDetails = UITools.Manager.ShowDetails; string text = calltipDef + ASDocumentation.GetTipDetails(calltipMember, paramName); - UITools.CallTip.CallTipShow(Sci, calltipPos - calltipOffset, text, forceRedraw); + CompletionList.CallTip.CallTipShow(calltipPos - calltipOffset, text, forceRedraw); } // highlight - if ((start < 0) || (end < 0)) UITools.CallTip.CallTipSetHlt(0, 0, true); - else UITools.CallTip.CallTipSetHlt(start + 1, end, true); + if ((start < 0) || (end < 0)) CompletionList.CallTip.CallTipSetHlt(0, 0, true); + else CompletionList.CallTip.CallTipSetHlt(start + 1, end, true); } static private int FindNearSymbolInFunctDef(string defBody, char symbol, int startAt) @@ -1460,7 +1478,7 @@ static public bool HandleFunctionCompletion(ScintillaControl Sci, bool autoHide, ShowCalltip(Sci, paramIndex, forceRedraw); return true; } - else UITools.CallTip.Hide(); + else CompletionList.CallTip.Hide(); } if (!ResolveFunction(Sci, position, autoHide)) diff --git a/External/Plugins/ASCompletion/Context/ASContext.cs b/External/Plugins/ASCompletion/Context/ASContext.cs index 1a65106e2b..4f0e9bb4ff 100644 --- a/External/Plugins/ASCompletion/Context/ASContext.cs +++ b/External/Plugins/ASCompletion/Context/ASContext.cs @@ -79,13 +79,24 @@ static public IMainForm MainForm get { return PluginBase.MainForm; } } + private static WeakReference curSciControl; static public ScintillaControl CurSciControl { get { + if (curSciControl != null && curSciControl.IsAlive) + return (ScintillaNet.ScintillaControl) curSciControl.Target; + ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; return doc != null ? doc.SciControl : null; } + internal set + { + // Used for ASCompletionBackend. + // Maybe another option would be to inherit HaxeContext and modify the DotContextResolved call. One question + // would be where to place ASCompletionBackend. + curSciControl = value != null ? new WeakReference(value) : null; + } } static public PluginUI Panel diff --git a/External/Plugins/ASCompletion/Helpers/ASCompletionBackend.cs b/External/Plugins/ASCompletion/Helpers/ASCompletionBackend.cs new file mode 100644 index 0000000000..90e2c73348 --- /dev/null +++ b/External/Plugins/ASCompletion/Helpers/ASCompletionBackend.cs @@ -0,0 +1,408 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Text; +using System.Windows.Forms; +using ASCompletion.Completion; +using PluginCore; +using PluginCore.Controls; + +namespace ASCompletion.Helpers +{ + + /** + * Helper class to overcome some ASComplete design choices: + * - Some methods that may be helpful are either private or directly call the default CompletionList instead of returning results. + * - Being a fully static class complicates some reuse. + * - It needs a Scintilla control to work. + * + * Making the current ASComplete.CompletionList to be interchangeable is the fastest and easiest way to achieve what we want. + * + * Another rather particular case is that of Haxe. To use the compiler output or the completion server we use a temporal file. + */ + public class ASCompletionListBackend : IDisposable, IEventHandler + { + + // Current code file state + private ScintillaNet.ScintillaControl sci; + private string currentLine; + private BackendFileInfo lastFileInfo; + + // ASCompletion state + private ASCompletion.Context.IASContext context; + private string contextFile; + private int contextLine; + private int memberPosition; + + // Haxe backend file + private string haxeTmpFile; + + private CompletionListControl completionList; + private IBackendFileGetter backendFileGetter; + + public ASCompletionListBackend(CompletionListControl completionList, IBackendFileGetter backendFileGetter) + { + if (completionList == null) + throw new ArgumentNullException("completionList"); + + if (backendFileGetter == null) + throw new ArgumentNullException("backendFileGetter"); + + this.completionList = completionList; + this.backendFileGetter = backendFileGetter; + + this.completionList.CallTip.OnShowing += CallTip_OnShowing; + this.completionList.CallTip.OnUpdateCallTip += CallTip_OnUpdateCallTip; + this.completionList.Host.KeyPress += CompletionListHost_KeyPress; + } + + public void ShowAutoCompletionList() + { + if (!UpdateCompletionBackend()) + { + CleanBackend(); + return; + } + + if (haxeTmpFile != null) + PluginCore.Helpers.FileHelper.WriteFile(haxeTmpFile, sci.Text, sci.Encoding, sci.SaveBOM); + + ASComplete.OnChar(sci, '.', true); + } + + public void ShowFunctionDetails() + { + if (!UpdateCompletionBackend()) + { + CleanBackend(); + return; + } + + if (haxeTmpFile != null) + PluginCore.Helpers.FileHelper.WriteFile(haxeTmpFile, sci.Text, sci.Encoding, sci.SaveBOM); + + ASComplete.HandleFunctionCompletion(sci, true); + } + + public bool SetCompletionBackend(BackendFileInfo file, string expression) + { + var info = backendFileGetter.GetFileContent(file); + if (info == null || info.CodePage == -1) + return false; + + if (sci != null && sci.ConfigurationLanguage == "haxe" && haxeTmpFile != null) + { + PluginCore.Managers.EventManager.RemoveEventHandler(this); + if (File.Exists(haxeTmpFile)) + File.Delete(haxeTmpFile); + haxeTmpFile = null; + } + + sci = new ScintillaNet.ScintillaControl(); + sci.Text = info.Contents; + sci.CodePage = info.CodePage; + sci.Encoding = Encoding.GetEncoding(info.CodePage); + sci.ConfigurationLanguage = PluginBase.CurrentProject.Language; + + if (sci.ConfigurationLanguage == "haxe") + { + string tmpFile = Path.Combine(Path.GetDirectoryName(file.File), + Path.GetFileNameWithoutExtension(file.File) + "___" + + Path.GetExtension(file.File)); + if (!File.Exists(tmpFile)) + { + haxeTmpFile = tmpFile; + sci.FileName = haxeTmpFile; + PluginCore.Managers.EventManager.AddEventHandler(this, EventType.FileSaving); + ASCompletion.Context.ASContext.CurSciControl = sci; + } + } + + currentLine = sci.GetLine(file.Line); + sci.CurrentPos = sci.PositionFromLine(file.Line); + sci.SetSel(sci.CurrentPos, sci.CurrentPos); + sci.ReplaceSel(expression); + + context = ASCompletion.Context.ASContext.Context; + contextFile = context.CurrentFile; + contextLine = context.CurrentLine; + var newContext = ASCompletion.Context.ASContext.GetLanguageContext(PluginBase.CurrentProject.Language); + newContext.CurrentFile = file.File; + newContext.CurrentLine = sci.CurrentLine; + ASCompletion.Context.ASContext.Context = newContext; + + var language = ScintillaNet.ScintillaControl.Configuration.GetLanguage(sci.ConfigurationLanguage); + if (language != null) // Should we provide some custom string otherwise? + completionList.CharacterClass = language.characterclass.Characters; + + completionList.Host.LostFocus += CompletionListHost_LostFocus; + completionList.OnHidden += CompletionList_Hidden; + + ASComplete.CompletionList = completionList; + + lastFileInfo = file; + + return true; + } + + public bool UpdateCompletionBackend() + { + BackendFileInfo newFileInfo; + string expression; + + if (!backendFileGetter.GetFileInfo(out newFileInfo)) + return false; + + expression = backendFileGetter.GetExpression(); + if (expression == null) + return false; + + if (sci != null && lastFileInfo == newFileInfo) + { + sci.CurrentPos = sci.PositionFromLine(lastFileInfo.Line); + sci.SetSel(sci.CurrentPos, sci.CurrentPos + sci.LineLength(lastFileInfo.Line)); + sci.ReplaceSel(currentLine); + sci.SetSel(sci.CurrentPos, sci.CurrentPos); + sci.ReplaceSel(expression); + + return true; + } + + return SetCompletionBackend(newFileInfo, expression); + } + + private void CompletionList_Hidden(object sender, EventArgs e) + { + if (completionList.Host.Owner.ContainsFocus) + return; + + CleanBackend(); + } + + private void CompletionListHost_KeyPress(object sender, KeyPressEventArgs e) + { + char c = e.KeyChar; + + if (char.IsControl(c)) + return; + + BackendFileInfo file; + if (!backendFileGetter.GetFileInfo(out file)) + return; + + string expression = backendFileGetter.GetExpression(); + + if (expression == null) + return; + + bool active = completionList.Active; + // Hacky... the current CompletionListControl 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 how CompletionListControl works + e.Handled = true; + completionList.Host.SelectedText = new string(e.KeyChar, 1); + completionList.Host.SelectionStart++; + bool charHandled = active && completionList.OnChar(c); + if (!UpdateCompletionBackend()) + { + CleanBackend(); + return; + } + + if (!charHandled) + { + if (".,()".IndexOf(c) > -1 && haxeTmpFile != null) + PluginCore.Helpers.FileHelper.WriteFile(haxeTmpFile, sci.Text, sci.Encoding, sci.SaveBOM); + ASComplete.OnChar(sci, c, true); + } + } + + private void CompletionListHost_LostFocus(object sender, EventArgs e) + { + if (completionList.Active) + return; + + CleanBackend(); + } + + private void CallTip_OnShowing(object sender, CancelEventArgs e) + { + var callTip = (MethodCallTip)sender; + + if (callTip.MemberPosition == memberPosition) return; + + e.Cancel = true; + + memberPosition = completionList.Host.SelectionStart - (sci.CurrentPos - callTip.MemberPosition); + + completionList.Host.Owner.BeginInvoke(new Action(() => + { + int hlsStart = completionList.CallTip.CurrentHLStart; + int hlsEnd = completionList.CallTip.CurrentHLEnd; + callTip.CallTipShow(memberPosition, callTip.RawText); + callTip.CallTipSetHlt(hlsStart, hlsEnd); + })); + } + + private void CallTip_OnUpdateCallTip(Control owner, int position) + { + if (!UpdateCompletionBackend()) + { + completionList.CallTip.Hide(); + CleanBackend(); + + return; + } + + if (!ASComplete.HandleFunctionCompletion(sci, false, true)) + completionList.CallTip.Hide(); + else if (completionList.CallTip.MemberPosition != memberPosition) + { + int hlsStart = completionList.CallTip.CurrentHLStart; + int hlsEnd = completionList.CallTip.CurrentHLEnd; + memberPosition = completionList.Host.SelectionStart - (sci.CurrentPos - completionList.CallTip.MemberPosition); + completionList.CallTip.CallTipShow(memberPosition, completionList.CallTip.RawText); + completionList.CallTip.CallTipSetHlt(hlsStart, hlsEnd, true); + } + } + + private void CleanBackend() + { + completionList.Host.LostFocus -= CompletionListHost_LostFocus; + completionList.OnHidden -= CompletionList_Hidden; + if (context != null) + { + ASCompletion.Context.ASContext.Context = context; + context.CurrentFile = contextFile; + context.CurrentLine = contextLine; + context = null; + } + ASComplete.CompletionList = UITools.CompletionList; + lastFileInfo = default(BackendFileInfo); + if (sci != null) + { + sci.Dispose(); + sci = null; + } + if (haxeTmpFile != null) + { + PluginCore.Managers.EventManager.RemoveEventHandler(this); + if (File.Exists(haxeTmpFile)) + File.Delete(haxeTmpFile); + haxeTmpFile = null; + ASCompletion.Context.ASContext.CurSciControl = null; + } + } + + public void Dispose() + { + if (sci != null) + { + sci.Dispose(); + sci = null; + } + + if (context != null) + { + ASCompletion.Context.ASContext.Context = context; + context.CurrentFile = contextFile; + context.CurrentLine = contextLine; + context = null; + } + + if (completionList != null) + { + completionList.CallTip.OnShowing -= CallTip_OnShowing; + completionList.CallTip.OnUpdateCallTip -= CallTip_OnUpdateCallTip; + completionList.Host.KeyPress -= CompletionListHost_KeyPress; + completionList.Host.LostFocus -= CompletionListHost_LostFocus; + completionList.OnHidden -= CompletionList_Hidden; + + completionList.Hide(); + completionList = null; + + ASComplete.CompletionList = UITools.CompletionList; + } + + if (haxeTmpFile != null) + { + PluginCore.Managers.EventManager.RemoveEventHandler(this); + if (File.Exists(haxeTmpFile)) + File.Delete(haxeTmpFile); + haxeTmpFile = null; + ASCompletion.Context.ASContext.CurSciControl = null; + } + + backendFileGetter = null; + } + + void IEventHandler.HandleEvent(object sender, NotifyEvent e, HandlingPriority priority) + { + // Note: With this we avoid some unwanted saves on Haxe projects, but we also disable wanted ones through Ctrl+S if we're + //both "running" this class, and placed in MainForm. + TextEvent se = (TextEvent)e; + if (se.Value != lastFileInfo.File) return; + se.Handled = true; + try + { + sci.FileName = haxeTmpFile; + PluginCore.Helpers.FileHelper.WriteFile(haxeTmpFile, sci.Text, sci.Encoding, sci.SaveBOM); + } + catch (Exception) + { + } + } + + #region Used Types + + public struct BackendFileInfo + { + public string File { get; set; } + public int Line { get; set; } + public DateTime LastUpdate { get; set; } + + public static bool operator ==(BackendFileInfo a, BackendFileInfo b) + { + return a.File == b.File && a.Line == b.Line && a.LastUpdate == b.LastUpdate; + } + + public static bool operator !=(BackendFileInfo a, BackendFileInfo b) + { + return a.File != b.File || a.Line != b.Line || a.LastUpdate != b.LastUpdate; + } + + public bool Equals(BackendFileInfo other) + { + return this == other; + } + + public override bool Equals(object obj) + { + return obj is BackendFileInfo && this == (BackendFileInfo)obj; + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = (File != null ? File.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Line; + hashCode = (hashCode * 397) ^ LastUpdate.GetHashCode(); + return hashCode; + } + } + + } + + public interface IBackendFileGetter + { + + bool GetFileInfo(out BackendFileInfo file); + string GetExpression(); + PluginCore.Helpers.EncodingFileInfo GetFileContent(BackendFileInfo file); + + } + + } + + #endregion +} diff --git a/External/Plugins/ASCompletion/PluginMain.cs b/External/Plugins/ASCompletion/PluginMain.cs index 6d864b2cd1..06be855cb9 100644 --- a/External/Plugins/ASCompletion/PluginMain.cs +++ b/External/Plugins/ASCompletion/PluginMain.cs @@ -747,7 +747,6 @@ private void AddEventHandlers() PluginBase.MainForm.IgnoredKeys.Add(Keys.Control | Keys.Enter); PluginBase.MainForm.IgnoredKeys.Add(Keys.Space | Keys.Control | Keys.Alt); // complete project types PluginBase.MainForm.RegisterShortcutItem("Completion.ShowHelp", Keys.F1); - PluginBase.MainForm.RegisterShortcutItem("Completion.Delete", Keys.Back); // application events EventManager.AddEventHandler(this, eventMask); @@ -913,22 +912,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 db1d63c1c3..e747d0a9af 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/FlashDebugger/Controls/BreakPointUI.cs b/External/Plugins/FlashDebugger/Controls/BreakPointUI.cs index 04dd4aced1..2e5af04970 100644 --- a/External/Plugins/FlashDebugger/Controls/BreakPointUI.cs +++ b/External/Plugins/FlashDebugger/Controls/BreakPointUI.cs @@ -3,19 +3,20 @@ using System.Collections.Generic; using System.IO; using System.Drawing; +using System.Linq; using System.Text.RegularExpressions; using System.Windows.Forms; -using FlashDebugger.Properties; -using PluginCore.Localization; +using ASCompletion.Helpers; +using FlashDebugger.Controls; using PluginCore; +using PluginCore.Controls; using PluginCore.Helpers; -using System.Linq; +using PluginCore.Localization; using PluginCore.Managers; -using PluginCore.Controls; namespace FlashDebugger { - class BreakPointUI : DockPanelControl + class BreakPointUI : DockPanelControl, ASCompletionListBackend.IBackendFileGetter { private static ImageList imageList; @@ -26,7 +27,7 @@ class BreakPointUI : DockPanelControl private DataGridViewTextBoxColumn ColumnBreakPointFilePath; private DataGridViewTextBoxColumn ColumnBreakPointFileName; private DataGridViewTextBoxColumn ColumnBreakPointLine; - private DataGridViewTextBoxColumn ColumnBreakPointExp; + private DataGridViewTextBoxExColumn ColumnBreakPointExp; private ToolStripEx tsActions; private ToolStripButton tsbRemoveSelected; private ToolStripButton tsbRemoveFiltered; @@ -38,6 +39,10 @@ class BreakPointUI : DockPanelControl private ToolStripComboBoxEx tscbFilterColumns; private Color defaultColor; + // Auto Completion + private PluginCore.Controls.CompletionListControl completionList; + private ASCompletionListBackend completionBackend; + public BreakPointUI(PluginMain pluginMain, BreakPointManager breakPointManager) { init(); @@ -96,7 +101,7 @@ private void init() this.ColumnBreakPointFilePath = new DataGridViewTextBoxColumn(); this.ColumnBreakPointFileName = new DataGridViewTextBoxColumn(); this.ColumnBreakPointLine = new DataGridViewTextBoxColumn(); - this.ColumnBreakPointExp = new DataGridViewTextBoxColumn(); + this.ColumnBreakPointExp = new DataGridViewTextBoxExColumn(); this.ColumnBreakPointEnable.HeaderText = TextHelper.GetString("Label.Enable"); this.ColumnBreakPointEnable.Name = "Enable"; this.ColumnBreakPointEnable.AutoSizeMode = DataGridViewAutoSizeColumnMode.None; @@ -127,9 +132,10 @@ private void init() } defaultColor = dgv.Rows[dgv.Rows.Add()].DefaultCellStyle.BackColor; dgv.Rows.Clear(); - this.dgv.CellEndEdit += new DataGridViewCellEventHandler(dgv_CellEndEdit); - this.dgv.CellMouseUp += new DataGridViewCellMouseEventHandler(dgv_CellMouseUp); - this.dgv.CellDoubleClick += new DataGridViewCellEventHandler(dgv_CellDoubleClick); + this.dgv.EditingControlShowing += dgv_EditingControlShowing; + this.dgv.CellEndEdit += dgv_CellEndEdit; + this.dgv.CellMouseUp += dgv_CellMouseUp; + this.dgv.CellDoubleClick += dgv_CellDoubleClick; this.Controls.Add(this.dgv); InitializeComponent(); tsbRemoveSelected.Image = imageList.Images["DeleteBreakpoint"]; @@ -183,6 +189,20 @@ void dgv_CellDoubleClick(object sender, DataGridViewCellEventArgs e) } } + void dgv_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) + { + if (ColumnBreakPointExp == dgv.CurrentCell.OwningColumn) + { + var listHost = + new TextBoxTarget((TextBoxEx) e.Control); + completionList = new PluginCore.Controls.CompletionListControl(listHost); + // We need this because MainForm.Activated event handler gives some conflicts + completionList.Tip.Selectable = completionList.CallTip.Selectable = false; + completionBackend = new ASCompletionListBackend(completionList, this); + e.Control.PreviewKeyDown += EditingControl_PreviewKeyDown; + } + } + void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex < 0) return; @@ -193,6 +213,31 @@ void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e) int line = int.Parse((string)dgv.Rows[e.RowIndex].Cells["Line"].Value); string exp = (string)dgv.Rows[e.RowIndex].Cells["Exp"].Value; breakPointManager.SetBreakPointCondition(filename, line - 1, exp); + + if (completionBackend != null) + { + completionList.Host.Owner.PreviewKeyDown -= EditingControl_PreviewKeyDown; + completionBackend.Dispose(); + completionBackend = null; + completionList = null; + } + } + } + + void EditingControl_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) + { + if (completionList != null && completionList.Active) + { + switch (e.KeyData) + { + case Keys.Enter: + case Keys.Escape: + case Keys.Down: + case Keys.Up: + case Keys.Tab: + e.IsInputKey = true; + break; + } } } @@ -512,6 +557,63 @@ private void TstxtFilter_KeyDown(object sender, KeyEventArgs e) } } + #region Auto Completion + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + // Ctrl+Space is detected at the form level instead of the editor level, so when we are docked we need to catch it before + if (keyData == (Keys.Control | Keys.Space) || keyData == (Keys.Control | Keys.Shift | Keys.Space)) + { + var box = dgv.EditingControl as DataGridViewTextBoxExEditingControl; + if (box != null && completionBackend != null) + { + if (keyData == (Keys.Control | Keys.Space)) + completionBackend.ShowAutoCompletionList(); + else + completionBackend.ShowFunctionDetails(); + + return true; + } + } + return base.ProcessCmdKey(ref msg, keyData); + } + + string ASCompletionListBackend.IBackendFileGetter.GetExpression() + { + var box = (TextBox) dgv.EditingControl; + return box.Text.Substring(0, box.SelectionStart); + } + + bool ASCompletionListBackend.IBackendFileGetter.GetFileInfo(out ASCompletionListBackend.BackendFileInfo fileInfo) + { + fileInfo = default(ASCompletionListBackend.BackendFileInfo); + string filePath = (string) dgv.CurrentRow.Cells[ColumnBreakPointFilePath.Name].Value; + + if (!File.Exists(filePath)) return false; + + fileInfo.Line = int.Parse((string)dgv.CurrentRow.Cells[ColumnBreakPointLine.Name].Value) - 1; + fileInfo.File = filePath; + fileInfo.LastUpdate = File.GetLastWriteTime(filePath); + + return true; + } + + EncodingFileInfo ASCompletionListBackend.IBackendFileGetter.GetFileContent(ASCompletionListBackend.BackendFileInfo file) + { + foreach (ITabbedDocument doc in PluginBase.MainForm.Documents) + { + if (doc.IsEditable && doc.FileName.ToUpper() == file.File.ToUpper()) + { + var sci = doc.SciControl; + return new EncodingFileInfo {Contents = sci.Text, CodePage = sci.CodePage}; + } + } + + return FileHelper.GetEncodingFileInfo(file.File); + } + + #endregion + } } diff --git a/External/Plugins/FlashDebugger/Controls/DataGridViewTextBoxExColumn.cs b/External/Plugins/FlashDebugger/Controls/DataGridViewTextBoxExColumn.cs new file mode 100644 index 0000000000..ff26c2f214 --- /dev/null +++ b/External/Plugins/FlashDebugger/Controls/DataGridViewTextBoxExColumn.cs @@ -0,0 +1,314 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using PluginCore.Controls; + +namespace FlashDebugger.Controls +{ + public class DataGridViewTextBoxExColumn : DataGridViewColumn + { + + public override DataGridViewCell CellTemplate + { + get { return base.CellTemplate; } + set + { + if (value != null && !(value is DataGridViewTextBoxExCell)) + { + throw new InvalidCastException("Cell type is not based upon the DataGridViewTextBoxExCell"); + } + base.CellTemplate = value; + } + } + + [System.ComponentModel.DefaultValue(32767)] + public int MaxInputLength + { + get + { + return ((DataGridViewTextBoxExCell)CellTemplate).MaxInputLength; + } + set + { + if (MaxInputLength != value) + { + ((DataGridViewTextBoxExCell)CellTemplate).MaxInputLength = value; + if (DataGridView != null) + { + DataGridViewRowCollection rows = DataGridView.Rows; + int count = rows.Count; + for (int i = 0; i < count; i++) + { + DataGridViewRow dataGridViewRow = rows.SharedRow(i); + var item = dataGridViewRow.Cells[Index] as DataGridViewTextBoxExCell; + if (item != null) + { + item.MaxInputLength = value; + } + } + } + } + } + } + + public DataGridViewTextBoxExColumn() + : base(new DataGridViewTextBoxExCell()) + { + SortMode = DataGridViewColumnSortMode.Automatic; + } + } + + public class DataGridViewTextBoxExCell : DataGridViewTextBoxCell + { + + private TextBox editingControl; + + public override Type EditType + { + get { return typeof(DataGridViewTextBoxExEditingControl); } + } + + private int maxInputLength = 32767; + public override int MaxInputLength + { + get { return maxInputLength; } + set + { + if (maxInputLength == value) return; + + maxInputLength = value; + if (editingControl != null && RowIndex == ((IDataGridViewEditingControl)editingControl).EditingControlRowIndex) + editingControl.MaxLength = value; + } + } + + public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) + { + base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); + + var ctl = DataGridView.EditingControl as TextBox; + if (ctl != null) + { + ctl.MaxLength = maxInputLength; + ctl.AcceptsReturn = ctl.Multiline = dataGridViewCellStyle.WrapMode == DataGridViewTriState.True; + ctl.BorderStyle = BorderStyle.None; + if (initialFormattedValue != null) ctl.Text = (string)initialFormattedValue; + editingControl = ctl; + } + } + + public override void DetachEditingControl() + { + base.DetachEditingControl(); + editingControl = null; + } + + } + + public class DataGridViewTextBoxExEditingControl : TextBoxEx, IDataGridViewEditingControl + { + + private static readonly DataGridViewContentAlignment anyRight = DataGridViewContentAlignment.BottomRight | + DataGridViewContentAlignment.MiddleRight | + DataGridViewContentAlignment.TopRight; + + private static readonly DataGridViewContentAlignment anyCenter = DataGridViewContentAlignment.BottomCenter | + DataGridViewContentAlignment.MiddleCenter | + DataGridViewContentAlignment.TopCenter; + + private static readonly DataGridViewContentAlignment anyTop = DataGridViewContentAlignment.TopCenter | + DataGridViewContentAlignment.TopLeft | + DataGridViewContentAlignment.TopRight; + + private Keys lastInputKey; + private bool repositionOnValueChange; + + public DataGridViewTextBoxExEditingControl() + { + TabStop = false; + } + + #region IDataGridViewEditingControl Members + + public bool RepositionEditingControlOnValueChange + { + get { return repositionOnValueChange; } + } + + public DataGridView EditingControlDataGridView { get; set; } + + public object EditingControlFormattedValue + { + get { return Text; } + set { Text = (string)value; } + } + + public int EditingControlRowIndex { get; set; } + + public bool EditingControlValueChanged { get; set; } + + public Cursor EditingPanelCursor + { + get { return Cursors.Default; } + } + + public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle) + { + Font = dataGridViewCellStyle.Font; + if (dataGridViewCellStyle.BackColor.A < 255) + { + Color color = Color.FromArgb(255, dataGridViewCellStyle.BackColor); + BackColor = color; + EditingControlDataGridView.EditingPanel.BackColor = color; + } + else + { + BackColor = dataGridViewCellStyle.BackColor; + } + ForeColor = dataGridViewCellStyle.ForeColor; + if (dataGridViewCellStyle.WrapMode == DataGridViewTriState.True) + { + WordWrap = true; + } + TextAlign = TranslateAlignment(dataGridViewCellStyle.Alignment); + repositionOnValueChange = dataGridViewCellStyle.WrapMode == DataGridViewTriState.True && + (dataGridViewCellStyle.Alignment & anyTop) == 0; + } + + public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey) + { + if (keyData == lastInputKey) return true; + Keys key = keyData & Keys.KeyCode; + if (key != Keys.Return) + { + switch (key) + { + case Keys.Prior: + case Keys.Next: + if (!EditingControlValueChanged) + { + break; + } + return true; + case Keys.End: + case Keys.Home: + if (this.SelectionLength == this.Text.Length) + { + break; + } + return true; + case Keys.Left: + if ((this.RightToLeft != RightToLeft.No || this.SelectionLength == 0 && base.SelectionStart == 0) && (this.RightToLeft != RightToLeft.Yes || this.SelectionLength == 0 && base.SelectionStart == this.Text.Length)) + { + break; + } + return true; + case Keys.Up: + if (this.Text.IndexOf("\r\n") < 0 || base.SelectionStart + this.SelectionLength < this.Text.IndexOf("\r\n")) + { + break; + } + return true; + case Keys.Right: + if ((this.RightToLeft != RightToLeft.No || this.SelectionLength == 0 && base.SelectionStart == this.Text.Length) && (this.RightToLeft != RightToLeft.Yes || this.SelectionLength == 0 && base.SelectionStart == 0)) + { + break; + } + return true; + case Keys.Down: + int selectionStart = base.SelectionStart + this.SelectionLength; + if (this.Text.IndexOf("\r\n", selectionStart) == -1) + { + break; + } + return true; + case Keys.Delete: + if (this.SelectionLength <= 0 && base.SelectionStart >= this.Text.Length) + { + break; + } + return true; + } + } + else if ((keyData & (Keys.Shift | Keys.Control | Keys.Alt)) == Keys.Shift && this.Multiline && base.AcceptsReturn) + { + return true; + } + return !dataGridViewWantsInputKey; + } + + public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context) + { + return Text; + } + + public void PrepareEditingControlForEdit(bool selectAll) + { + if (selectAll) + SelectAll(); + else + SelectionStart = Text.Length; + } + + #endregion + + private void NotifyDataGridViewOfValueChange() + { + EditingControlValueChanged = true; + EditingControlDataGridView.NotifyCurrentCellDirty(true); + } + + private static HorizontalAlignment TranslateAlignment(DataGridViewContentAlignment align) + { + if ((align & anyRight) != 0) + return HorizontalAlignment.Right; + + if ((align & anyCenter) != 0) + return HorizontalAlignment.Center; + + return HorizontalAlignment.Left; + } + + protected override void OnTextChanged(EventArgs e) + { + base.OnTextChanged(e); + NotifyDataGridViewOfValueChange(); + } + + protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e) + { + base.OnPreviewKeyDown(e); + + lastInputKey = e.IsInputKey ? e.KeyData : Keys.None; + } + + protected override bool ProcessKeyEventArgs(ref Message m) + { + Keys wParam = (Keys)((int)m.WParam); + if (wParam == Keys.LineFeed) + { + if (m.Msg == 258 && Control.ModifierKeys == Keys.Control && this.Multiline && base.AcceptsReturn) + { + return true; + } + } + else if (wParam != Keys.Return) + { + if (wParam == Keys.A) + { + if (m.Msg == 256 && Control.ModifierKeys == Keys.Control) + { + base.SelectAll(); + return true; + } + } + } + else if (m.Msg == 258 && (Control.ModifierKeys != Keys.Shift || !this.Multiline || !base.AcceptsReturn)) + { + return true; + } + return base.ProcessKeyEventArgs(ref m); + } + } + +} diff --git a/External/Plugins/FlashDebugger/Controls/DataTree/NodeControls/NodeTextBoxEx.cs b/External/Plugins/FlashDebugger/Controls/DataTree/NodeControls/NodeTextBoxEx.cs new file mode 100644 index 0000000000..3e29312fd7 --- /dev/null +++ b/External/Plugins/FlashDebugger/Controls/DataTree/NodeControls/NodeTextBoxEx.cs @@ -0,0 +1,150 @@ +/* + * Control created to replace NodeTextBox to overcome some limitations: + * - There is no proper event to know when the editor is created, although there are some limited workarounds + * - There is no way to prevent Enter and Escape behaviour in a satisfactory way. TreeViewAdv should override some of the Key methods, and + * check with the current EditableControl what to do. This would allow us to use Handled or IsInputKey. DGV works this way basically. + * - Due to how CallTip works, a simple TextBox isn't enough. + */ + +using System; +using System.Drawing; +using System.Windows.Forms; +using Aga.Controls.Tree; +using Aga.Controls.Tree.NodeControls; +using PluginCore.Controls; + +namespace FlashDebugger.Controls.DataTree.NodeControls +{ + public class NodeTextBoxEx : BaseTextControl + { + private const int MinTextBoxWidth = 30; + + protected override Size CalculateEditorSize(EditorContext context) + { + if (Parent.UseColumns) + return context.Bounds.Size; + else + { + Size size = GetLabelSize(context.CurrentNode, context.DrawContext, _label); + int width = Math.Max(size.Width + Font.Height, MinTextBoxWidth); // reserve a place for new typed character + return new Size(width, size.Height); + } + } + + public override void KeyDown(KeyEventArgs args) + { + if (args.KeyCode == Keys.F2 && Parent.CurrentNode != null && EditEnabled) + { + args.Handled = true; + BeginEdit(); + } + } + + protected override Control CreateEditor(TreeNodeAdv node) + { + var textBox = CreateTextBox(); + OnEditorCreated(new ControlEventArgs(textBox)); + textBox.TextAlign = TextAlign; + textBox.Text = GetLabel(node); + textBox.BorderStyle = BorderStyle.FixedSingle; + textBox.TextChanged += EditorTextChanged; + _label = textBox.Text; + SetEditControlProperties(textBox, node); + textBox.PreviewKeyDown += new PreviewKeyDownEventHandler(textBox_PreviewKeyDown); + textBox.KeyPosted += textBox_KeyPosted; + return textBox; + } + + void textBox_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Enter: + case Keys.Escape: + e.IsInputKey = true; + break; + } + } + + void textBox_KeyPosted(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Escape) + EndEdit(false); + else if (e.KeyCode == Keys.Enter) + EndEdit(true); + } + + protected override void DisposeEditor(Control editor) + { + var textBox = editor as TextBoxEx; + textBox.TextChanged -= EditorTextChanged; + textBox.KeyPosted -= textBox_KeyPosted; + textBox.PreviewKeyDown -= textBox_PreviewKeyDown; + } + + private string _label; + private void EditorTextChanged(object sender, EventArgs e) + { + var textBox = sender as TextBox; + _label = textBox.Text; + Parent.UpdateEditorBounds(); + } + + protected virtual TextBoxEx CreateTextBox() + { + return new TextBoxEx(); + } + + protected override void DoApplyChanges(TreeNodeAdv node, Control editor) + { + var label = (editor as TextBox).Text; + string oldLabel = GetLabel(node); + if (oldLabel != label) + { + SetLabel(node, label); + OnLabelChanged(node.Tag, oldLabel, label); + } + } + + public override void Cut(Control control) + { + (control as TextBox).Cut(); + } + + public override void Copy(Control control) + { + (control as TextBox).Copy(); + } + + public override void Paste(Control control) + { + (control as TextBox).Paste(); + } + + public override void Delete(Control control) + { + var textBox = control as TextBox; + int len = Math.Max(textBox.SelectionLength, 1); + if (textBox.SelectionStart < textBox.Text.Length) + { + int start = textBox.SelectionStart; + textBox.Text = textBox.Text.Remove(textBox.SelectionStart, len); + textBox.SelectionStart = start; + } + } + + public event EventHandler LabelChanged; + protected void OnLabelChanged(object subject, string oldLabel, string newLabel) + { + if (LabelChanged != null) + LabelChanged(this, new LabelEventArgs(subject, oldLabel, newLabel)); + } + + public event ControlEventHandler EditorCreated; + protected virtual void OnEditorCreated(ControlEventArgs e) + { + if (EditorCreated != null) + EditorCreated(this, e); + } + } +} diff --git a/External/Plugins/FlashDebugger/Controls/DataTreeControl.Designer.cs b/External/Plugins/FlashDebugger/Controls/DataTreeControl.Designer.cs index 6398e01f2c..84cce6822b 100644 --- a/External/Plugins/FlashDebugger/Controls/DataTreeControl.Designer.cs +++ b/External/Plugins/FlashDebugger/Controls/DataTreeControl.Designer.cs @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License using Aga.Controls.Tree; using Aga.Controls.Tree.NodeControls; +using FlashDebugger.Controls.DataTree.NodeControls; namespace FlashDebugger.Controls { @@ -50,7 +51,7 @@ private void InitializeComponent() this._tree = new TreeViewAdv(); this.NameTreeColumn = new TreeColumn(); this.ValueTreeColumn = new Aga.Controls.Tree.TreeColumn(); - this.NameNodeTextBox = new NodeTextBox(); + this.NameNodeTextBox = new NodeTextBoxEx(); this.ValueNodeTextBox = new Aga.Controls.Tree.NodeControls.NodeTextBox(); this.SuspendLayout(); // @@ -122,7 +123,7 @@ private void InitializeComponent() private Aga.Controls.Tree.TreeViewAdv _tree; private Aga.Controls.Tree.TreeColumn NameTreeColumn; private Aga.Controls.Tree.TreeColumn ValueTreeColumn; - private Aga.Controls.Tree.NodeControls.NodeTextBox NameNodeTextBox; + private FlashDebugger.Controls.DataTree.NodeControls.NodeTextBoxEx NameNodeTextBox; private Aga.Controls.Tree.NodeControls.NodeTextBox ValueNodeTextBox; } } diff --git a/External/Plugins/FlashDebugger/Controls/DataTreeControl.cs b/External/Plugins/FlashDebugger/Controls/DataTreeControl.cs index 295aa9f74c..0b8650f7a4 100644 --- a/External/Plugins/FlashDebugger/Controls/DataTreeControl.cs +++ b/External/Plugins/FlashDebugger/Controls/DataTreeControl.cs @@ -1,10 +1,13 @@ using System; using System.Drawing; using System.Windows.Forms; +using ASCompletion.Helpers; using Aga.Controls.Tree; using System.Collections.Generic; using System.Collections.ObjectModel; using Aga.Controls.Tree.NodeControls; +using FlashDebugger.Controls.DataTree.NodeControls; +using PluginCore.Controls; using PluginCore.Managers; using flash.tools.debugger; using FlashDebugger.Controls.DataTree; @@ -14,7 +17,7 @@ namespace FlashDebugger.Controls { - public partial class DataTreeControl : UserControl, IToolTipProvider + public partial class DataTreeControl : UserControl, IToolTipProvider, ASCompletionListBackend.IBackendFileGetter { public event EventHandler ValueChanged; @@ -26,20 +29,24 @@ public partial class DataTreeControl : UserControl, IToolTipProvider private bool watchMode; private bool addingNewExpression; + // Autocompletion + private CompletionListControl completionList; + private ASCompletionListBackend completionBackend; + public Collection Nodes { get { return _model.Root.Nodes; } - } + } public TreeViewAdv Tree { get { return _tree; } - } + } public ViewerForm Viewer { get { return viewerForm; } - } + } public DataTreeControl() : this(false){} @@ -54,12 +61,14 @@ public DataTreeControl(bool watchMode) this.watchMode = true; NameNodeTextBox.EditEnabled = true; NameNodeTextBox.EditorShowing += NameNodeTextBox_EditorShowing; + NameNodeTextBox.EditorCreated += NameNodeTextBox_EditorCreated; NameNodeTextBox.EditorHided += NameNodeTextBox_EditorHided; NameNodeTextBox.IsEditEnabledValueNeeded += NameNodeTextBox_IsEditEnabledValueNeeded; NameNodeTextBox.LabelChanged += NameNodeTextBox_LabelChanged; _tree.KeyDown += Tree_KeyDown; _tree.NodeMouseClick += Tree_NameNodeMouseClick; } + _model = new DataTreeModel(); _tree.Model = _model; _tree.FullRowSelect = true; @@ -179,7 +188,7 @@ void NameNodeTextBox_EditorHided(object sender, EventArgs e) { if (addingNewExpression) { - NodeTextBox box = sender as NodeTextBox; + var box = sender as NodeTextBoxEx; var node = box.Parent.CurrentNode.Tag as Node; if (node.Text.Trim() == "") node.Text = TextHelper.GetString("Label.AddExpression"); addingNewExpression = false; @@ -190,7 +199,7 @@ void NameNodeTextBox_EditorHided(object sender, EventArgs e) void NameNodeTextBox_EditorShowing(object sender, System.ComponentModel.CancelEventArgs e) { - NodeTextBox box = sender as NodeTextBox; + var box = sender as NodeTextBoxEx; var node = box.Parent.CurrentNode.Tag as Node; if (box.Parent.CurrentNode.NextNode == null) { @@ -200,6 +209,15 @@ void NameNodeTextBox_EditorShowing(object sender, System.ComponentModel.CancelEv else addingNewExpression = false; } + void NameNodeTextBox_EditorCreated(object sender, ControlEventArgs e) + { + var controlHost = new TextBoxTarget((TextBoxEx)e.Control); + completionList = new CompletionListControl(controlHost); + // We need this because TreeViewAdv handles the control LostFocus event itself and the resulting behaviour doesn't seeem very nice + completionList.Tip.Selectable = completionList.CallTip.Selectable = false; + completionBackend = new ASCompletionListBackend(completionList, this); + } + void NameNodeTextBox_IsEditEnabledValueNeeded(object sender, NodeControlValueEventArgs e) { e.Value = e.Node.Level == 1; @@ -207,7 +225,7 @@ void NameNodeTextBox_IsEditEnabledValueNeeded(object sender, NodeControlValueEve void NameNodeTextBox_LabelChanged(object sender, LabelEventArgs e) { - NodeTextBox box = sender as NodeTextBox; + var box = sender as NodeTextBoxEx; if (box.Parent.CurrentNode == null) return; DataNode node = box.Parent.CurrentNode.Tag as DataNode; if (e.NewLabel.Trim() == "" || e.NewLabel.Trim() == TextHelper.GetString("Label.AddExpression")) @@ -341,36 +359,37 @@ public string GetFullPath(DataNode node) private void CopyItemClick(Object sender, System.EventArgs e) { - DataNode node = Tree.SelectedNode.Tag as DataNode; - Clipboard.SetText(string.Format("{0} = {1}",node.Text, node.Value)); + DataNode node = Tree.SelectedNode.Tag as DataNode; + Clipboard.SetText(string.Format("{0} = {1}",node.Text, node.Value)); } + private void ViewerItemClick(Object sender, System.EventArgs e) { - if (viewerForm == null) - { - viewerForm = new ViewerForm(); - viewerForm.StartPosition = FormStartPosition.Manual; - } - DataNode node = Tree.SelectedNode.Tag as DataNode; - viewerForm.Exp = node.Text; - if (node is ValueNode) - { - var vNode = (ValueNode)node; - // use IsEditing to get unfiltered value - bool ed = vNode.IsEditing; - vNode.IsEditing = true; - viewerForm.Value = node.Value; - vNode.IsEditing = ed; - } - else - { - viewerForm.Value = node.Value; + if (viewerForm == null) + { + viewerForm = new ViewerForm(); + viewerForm.StartPosition = FormStartPosition.Manual; + } + DataNode node = Tree.SelectedNode.Tag as DataNode; + viewerForm.Exp = node.Text; + if (node is ValueNode) + { + var vNode = (ValueNode)node; + // use IsEditing to get unfiltered value + bool ed = vNode.IsEditing; + vNode.IsEditing = true; + viewerForm.Value = node.Value; + vNode.IsEditing = ed; + } + else + { + viewerForm.Value = node.Value; + } + Form mainform = (PluginBase.MainForm as Form); + viewerForm.Left = mainform.Left + mainform.Width / 2 - viewerForm.Width / 2; + viewerForm.Top = mainform.Top + mainform.Height / 2 - viewerForm.Height / 2; + viewerForm.ShowDialog(); } - Form mainform = (PluginBase.MainForm as Form); - viewerForm.Left = mainform.Left + mainform.Width / 2 - viewerForm.Width / 2; - viewerForm.Top = mainform.Top + mainform.Height / 2 - viewerForm.Height / 2; - viewerForm.ShowDialog(); - } private void WatchItemClick(Object sender, EventArgs e) { @@ -466,7 +485,7 @@ public void ListChildItems(ValueNode node) var ctx = new ExpressionContext(flashInterface.Session, flashInterface.GetFrames()[PluginMain.debugManager.CurrentFrame]); var obj = exp.evaluate(ctx); if (obj is flash.tools.debugger.concrete.DValue) obj = new flash.tools.debugger.concrete.DVariable("getChildAt(" + i + ")", (flash.tools.debugger.concrete.DValue)obj, ((flash.tools.debugger.concrete.DValue)obj).getIsolateId()); - DataNode childNode = new VariableNode((Variable) obj) + DataNode childNode = new VariableNode((Variable)obj) { HideClassId = PluginMain.settingObject.HideClassIds, HideFullClasspath = PluginMain.settingObject.HideFullClasspaths @@ -635,6 +654,86 @@ public string GetToolTip(TreeNodeAdv node, NodeControl nodeControl) #endregion + #region Auto Completion + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + // Ctrl+Space is detected at the form level instead of the editor level, so when we are docked we need to catch it before + if (keyData == (Keys.Control | Keys.Space) || keyData == (Keys.Control | Keys.Shift | Keys.Space)) + { + var box = Tree.CurrentEditor as TextBoxEx; + if (box != null) + { + if (keyData == (Keys.Control | Keys.Space)) + completionBackend.ShowAutoCompletionList(); + else + completionBackend.ShowFunctionDetails(); + + return true; + } + } + return base.ProcessCmdKey(ref msg, keyData); + } + + PluginCore.Helpers.EncodingFileInfo ASCompletionListBackend.IBackendFileGetter.GetFileContent(ASCompletionListBackend.BackendFileInfo file) + { + var debugger = PluginMain.debugManager.FlashInterface; + if (!debugger.isDebuggerStarted || !debugger.isDebuggerSuspended || PluginMain.debugManager.CurrentFrame >= debugger.GetFrames().Length) + return null; + var location = debugger.GetFrames()[PluginMain.debugManager.CurrentFrame].getLocation(); + + // Could somebody want to pass a file pointing to a file different to the one being debugged? highly unlikely + + var sourceFile = location.getFile(); + var sourceFileText = new System.Text.StringBuilder(); + if (sourceFile != null) + { + for (int i = 1, count = sourceFile.getLineCount(); i <= count; i++) + { + sourceFileText.Append(sourceFile.getLine(i).ToString()).Append(PluginCore.Utilities.LineEndDetector.GetNewLineMarker((int)PluginBase.Settings.EOLMode)); + } + } + + if (sourceFileText.Length == 0) + { + if (file.File != null && System.IO.File.Exists(file.File) && + file.File == PluginMain.debugManager.GetLocalPath(sourceFile)) + { + // Notify the user of this case? + //MessageBox.Show("Source code not available, but potential matching file found on disk, do you want to use it?"); + return PluginCore.Helpers.FileHelper.GetEncodingFileInfo(file.File); + } + + return null; + } + + // Maybe we should convert from UTF-16 to UTF-8? no problems so far + return new PluginCore.Helpers.EncodingFileInfo { CodePage = System.Text.Encoding.UTF8.CodePage, Contents = sourceFileText.ToString() }; + } + + bool ASCompletionListBackend.IBackendFileGetter.GetFileInfo(out ASCompletionListBackend.BackendFileInfo file) + { + file = default(ASCompletionListBackend.BackendFileInfo); + var debugger = PluginMain.debugManager.FlashInterface; + if (!debugger.isDebuggerStarted || !debugger.isDebuggerSuspended || PluginMain.debugManager.CurrentFrame >= debugger.GetFrames().Length) + return false; + var location = debugger.GetFrames()[PluginMain.debugManager.CurrentFrame].getLocation(); + file.File = PluginMain.debugManager.GetLocalPath(location.getFile()); + if (file.File == null && (location.getFile() == null || location.getFile().getLineCount() == 0)) + return false; + + file.Line = location.getLine() - 1; + + return true; + } + + string ASCompletionListBackend.IBackendFileGetter.GetExpression() + { + return Tree.CurrentEditor.Text.Substring(0, ((TextBoxEx)Tree.CurrentEditor).SelectionStart); + } + + #endregion + #region State Class private class DataTreeState @@ -658,7 +757,7 @@ private void CopyItemValueClick(Object sender, System.EventArgs e) Clipboard.SetText(value); else Clipboard.Clear(); - } + } private void CopyItemIdClick(Object sender, System.EventArgs e) { @@ -668,7 +767,7 @@ private void CopyItemIdClick(Object sender, System.EventArgs e) Clipboard.SetText(node.Id); else Clipboard.Clear(); - } +} private void CopyItemTreeClick(Object sender, System.EventArgs e) { diff --git a/External/Plugins/FlashDebugger/Controls/ImmediateUI.Designer.cs b/External/Plugins/FlashDebugger/Controls/ImmediateUI.Designer.cs index 258b0e2348..3918ea7df5 100644 --- a/External/Plugins/FlashDebugger/Controls/ImmediateUI.Designer.cs +++ b/External/Plugins/FlashDebugger/Controls/ImmediateUI.Designer.cs @@ -29,7 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - this.textBox = new System.Windows.Forms.TextBox(); + this.textBox = new PluginCore.Controls.TextBoxEx(); this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); this.cutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -46,10 +46,11 @@ private void InitializeComponent() this.textBox.Location = new System.Drawing.Point(0, 0); this.textBox.Multiline = true; this.textBox.Name = "textBox"; + this.textBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.textBox.Size = new System.Drawing.Size(148, 150); this.textBox.TabIndex = 0; this.textBox.WordWrap = false; - this.textBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox_KeyDown); + this.textBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextBox_KeyDown); this.textBox.BorderStyle = System.Windows.Forms.BorderStyle.None; // // contextMenuStrip1 @@ -68,21 +69,21 @@ private void InitializeComponent() this.cutToolStripMenuItem.Name = "cutToolStripMenuItem"; this.cutToolStripMenuItem.Size = new System.Drawing.Size(118, 22); this.cutToolStripMenuItem.Text = "Cut"; - this.cutToolStripMenuItem.Click += new System.EventHandler(this.cutToolStripMenuItem_Click); + this.cutToolStripMenuItem.Click += new System.EventHandler(this.CutToolStripMenuItem_Click); // // copyToolStripMenuItem // this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; this.copyToolStripMenuItem.Size = new System.Drawing.Size(118, 22); this.copyToolStripMenuItem.Text = "Copy"; - this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click); + this.copyToolStripMenuItem.Click += new System.EventHandler(this.CopyToolStripMenuItem_Click); // // pasteToolStripMenuItem // this.pasteToolStripMenuItem.Name = "pasteToolStripMenuItem"; this.pasteToolStripMenuItem.Size = new System.Drawing.Size(118, 22); this.pasteToolStripMenuItem.Text = "Paste"; - this.pasteToolStripMenuItem.Click += new System.EventHandler(this.pasteToolStripMenuItem_Click); + this.pasteToolStripMenuItem.Click += new System.EventHandler(this.PasteToolStripMenuItem_Click); // // toolStripSeparator1 // @@ -94,7 +95,7 @@ private void InitializeComponent() this.clearAllToolStripMenuItem.Name = "clearAllToolStripMenuItem"; this.clearAllToolStripMenuItem.Size = new System.Drawing.Size(118, 22); this.clearAllToolStripMenuItem.Text = "Clear All"; - this.clearAllToolStripMenuItem.Click += new System.EventHandler(this.clearAllToolStripMenuItem_Click); + this.clearAllToolStripMenuItem.Click += new System.EventHandler(this.ClearAllToolStripMenuItem_Click); // // ImmediateUI // @@ -110,7 +111,7 @@ private void InitializeComponent() #endregion - private System.Windows.Forms.TextBox textBox; + private PluginCore.Controls.TextBoxEx textBox; private System.Windows.Forms.ContextMenuStrip contextMenuStrip; private System.Windows.Forms.ToolStripMenuItem clearAllToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem; diff --git a/External/Plugins/FlashDebugger/Controls/ImmediateUI.cs b/External/Plugins/FlashDebugger/Controls/ImmediateUI.cs index 62ec658561..00e9e93411 100644 --- a/External/Plugins/FlashDebugger/Controls/ImmediateUI.cs +++ b/External/Plugins/FlashDebugger/Controls/ImmediateUI.cs @@ -1,86 +1,98 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; -using System.Data; using System.Text; using System.Windows.Forms; +using ASCompletion.Completion; +using ASCompletion.Helpers; +using ASCompletion.Model; using flash.tools.debugger; using flash.tools.debugger.expression; +using PluginCore.Controls; +using PluginCore; namespace FlashDebugger.Controls { - public partial class ImmediateUI : DockPanelControl + public partial class ImmediateUI : DockPanelControl, ASCompletionListBackend.IBackendFileGetter { private List history; private int historyPos; + private CompletionListControl completionList; + private ASCompletionListBackend completionBackend; + public ImmediateUI() { this.AutoKeyHandling = true; this.InitializeComponent(); this.contextMenuStrip.Renderer = new DockPanelStripRenderer(false); + this.completionList = new CompletionListControl(new TextBoxTarget(textBox)); + this.completionBackend = new ASCompletionListBackend(completionList, this); this.history = new List(); } - private void textBox_KeyDown(object sender, KeyEventArgs e) + private void TextBox_KeyDown(object sender, KeyEventArgs e) { + if (completionList.Active) return; if (e.KeyCode == Keys.Back && this.textBox.GetFirstCharIndexOfCurrentLine() == this.textBox.SelectionStart) e.SuppressKeyPress = true; - if (e.KeyCode == Keys.Up && this.historyPos > 0) + if (e.KeyCode == Keys.Up) { - this.historyPos--; - this.textBox.Select(this.textBox.Text.Length, 0); - this.textBox.Text = this.textBox.Text.Substring(0, this.textBox.GetFirstCharIndexOfCurrentLine()) + this.history[this.historyPos]; - try + if (textBox.GetLineFromCharIndex(textBox.SelectionStart) != textBox.Lines.Length - 1 || e.Modifiers > 0) + return; + if (this.historyPos > 0) { - this.textBox.Select(this.textBox.TextLength, 0); - this.textBox.ScrollToCaret(); + this.historyPos--; + this.textBox.Select(this.textBox.Text.Length, 0); + this.textBox.Text = this.textBox.Text.Substring(0, this.textBox.GetFirstCharIndexOfCurrentLine()) + this.history[this.historyPos]; + try + { + this.textBox.Select(this.textBox.TextLength, 0); + this.textBox.ScrollToCaret(); + } + catch { /* WineMod: not supported */ } } - catch { /* WineMod: not supported */ } + e.SuppressKeyPress = true; } - if (e.KeyCode == Keys.Down && this.historyPos + 1 < this.history.Count) + if (e.KeyCode == Keys.Down) { - this.historyPos++; - this.textBox.Select(this.textBox.Text.Length, 0); - this.textBox.Text = this.textBox.Text.Substring(0, this.textBox.GetFirstCharIndexOfCurrentLine()) + this.history[this.historyPos]; - try + if (textBox.GetLineFromCharIndex(textBox.SelectionStart) != textBox.Lines.Length - 1 || e.Modifiers > 0) + return; + if (this.historyPos + 1 < this.history.Count) { - this.textBox.Select(this.textBox.TextLength, 0); - this.textBox.ScrollToCaret(); + this.historyPos++; + this.textBox.Select(this.textBox.Text.Length, 0); + this.textBox.Text = this.textBox.Text.Substring(0, this.textBox.GetFirstCharIndexOfCurrentLine()) + this.history[this.historyPos]; + try + { + this.textBox.Select(this.textBox.TextLength, 0); + this.textBox.ScrollToCaret(); + } + catch { /* WineMod: not supported */ } } - catch { /* WineMod: not supported */ } + e.SuppressKeyPress = true; } - if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) e.SuppressKeyPress = true; if (e.KeyCode == Keys.Enter) { e.SuppressKeyPress = true; int curLine = this.textBox.GetLineFromCharIndex(this.textBox.SelectionStart); - //int curLine = 0; - //int tmp = 0; - //while (true) - //{ - // tmp += this.textBox.Lines[curLine].Length + 2; // newline chars - // if (tmp >= this.textBox.SelectionStart) break; - // curLine++; - //} string line = ""; - if (curLine 0 && !this.textBox.Lines[this.textBox.Lines.Length - 1].Trim().Equals("")) this.textBox.AppendText(Environment.NewLine); try { - this.history.Add(line); + if (history.Count == 0 || history[history.Count - 1] != line) + this.history.Add(line); this.historyPos = this.history.Count; if (line == "swfs") { - this.textBox.AppendText(processSwfs()); + this.textBox.AppendText(ProcessSwfs()); } else if (line.StartsWith("p ")) { - this.textBox.AppendText(processExpr(line.Substring(2))); + this.textBox.AppendText(ProcessExpr(line.Substring(2))); } else if (line.StartsWith("g ")) { - this.textBox.AppendText(processGlobal(line.Substring(2))); + this.textBox.AppendText(ProcessGlobal(line.Substring(2))); } else { @@ -103,7 +115,7 @@ private void textBox_KeyDown(object sender, KeyEventArgs e) { this.textBox.AppendText(!string.IsNullOrEmpty(ex.Message) ? ex.GetType().FullName + ": " + ex.Message : ex.ToString()); } - if (this.textBox.Lines.Length > 0 && !this.textBox.Lines[this.textBox.Lines.Length - 1].Trim().Equals("")) this.textBox.AppendText(Environment.NewLine); + if (this.textBox.Lines.Length > 0) this.textBox.AppendText(Environment.NewLine); try { this.textBox.Select(this.textBox.TextLength, 0); @@ -113,21 +125,113 @@ private void textBox_KeyDown(object sender, KeyEventArgs e) } } - private string processSwfs() + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { - StringBuilder ret = new StringBuilder(); + // Ctrl+Space is detected at the form level instead of the editor level, so when we are docked we need to catch it before + if (keyData == (Keys.Control | Keys.Space) || keyData == (Keys.Control | Keys.Shift | Keys.Space)) + { + int curLine = this.textBox.GetLineFromCharIndex(this.textBox.SelectionStart); + string line = (curLine < this.textBox.Lines.Length) ? this.textBox.Lines[curLine] : ""; + + if (curLine != textBox.Lines.Length - 1 || !line.StartsWith("p ") && !line.StartsWith("g ")) + return false; + + int lineLength = textBox.SelectionStart - textBox.GetFirstCharIndexFromLine(textBox.Lines.Length - 1) - 2; + if (lineLength < 0) + return false; + + ASCompletionListBackend.BackendFileInfo file; + if (!((ASCompletionListBackend.IBackendFileGetter)this).GetFileInfo(out file)) + return false; + + if (line.StartsWith("g ")) + { + string expression = line.Substring(2, lineLength).TrimStart(); + if (expression.IndexOfAny(".,_();:[]{}=-+*\'\"\\/|<>?! ".ToCharArray()) > -1) + return false; + + var fileModel = ASFileParser.ParseFile(new FileModel(file.File)); + + var members = new MemberList(); + + // We could use Context.GetTopLevelElements, but neither AS2 or super are supported... + members.Add(new MemberModel("this", "", FlagType.Variable | FlagType.Intrinsic, Visibility.Public)); + + // root types & packages + var newContext = ASCompletion.Context.ASContext.GetLanguageContext(PluginBase.CurrentProject.Language); + FileModel baseElements = newContext.ResolvePackage(null, false); + if (baseElements != null) + { + foreach (var m in baseElements.Members.Items) + { + members.Add(m); + } + foreach (var m in baseElements.Imports.Items) + { + if (m.Flags == FlagType.Package) continue; + members.Add(m); + } + } + + for (int i = fileModel.Classes.Count - 1; i >= 0; i--) + { + var c = fileModel.Classes[i]; + if (c.LineFrom <= file.Line && c.LineTo >= file.Line) + { + foreach (var m in c.Members.Items) + { + if (m.Access != Visibility.Public || (m.Flags & FlagType.Variable) == 0) continue; + members.Add(m); + } + + break; + } + } + + members.Sort(); + + var language = ScintillaNet.ScintillaControl.Configuration.GetLanguage(PluginBase.CurrentProject.Language); + if (language != null) // Should we provide some custom string otherwise? + completionList.CharacterClass = language.characterclass.Characters; + + members.Sort(); + var items = new List(); + foreach (var m in members.Items) items.Add(new MemberItem(m)); + completionList.Show(items, true, expression); + + return true; + } - foreach (SwfInfo info in PluginMain.debugManager.FlashInterface.Session.getSwfs()) + if (completionBackend.SetCompletionBackend(file, line.Substring(2, lineLength))) { - if (info == null) continue; - ret.Append(info.getPath()).Append("\tswfsize ").Append(info.getSwfSize()).Append("\tprocesscomplete ").Append(info.isProcessingComplete()) - .Append("\tunloaded ").Append(info.isUnloaded()).Append("\turl ").Append(info.getUrl()).Append("\tsourcecount ") - .Append(info.getSourceCount(PluginMain.debugManager.FlashInterface.Session)).AppendLine(); + if (keyData == (Keys.Control | Keys.Space)) + completionBackend.ShowAutoCompletionList(); + else + completionBackend.ShowFunctionDetails(); + + return true; } - return ret.ToString(); + + return false; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private string ProcessSwfs() + { + StringBuilder ret = new StringBuilder(); + + foreach (SwfInfo info in PluginMain.debugManager.FlashInterface.Session.getSwfs()) + { + if (info == null) continue; + ret.Append(info.getPath()).Append("\tswfsize ").Append(info.getSwfSize()).Append("\tprocesscomplete ").Append(info.isProcessingComplete()) + .Append("\tunloaded ").Append(info.isUnloaded()).Append("\turl ").Append(info.getUrl()).Append("\tsourcecount ") + .Append(info.getSourceCount(PluginMain.debugManager.FlashInterface.Session)).AppendLine(); + } + return ret.ToString(); } - private string processExpr(string expr) + private string ProcessExpr(string expr) { IASTBuilder builder = new ASTBuilder(true); ValueExp exp = builder.parse(new java.io.StringReader(expr)); @@ -138,7 +242,7 @@ private string processExpr(string expr) return obj.toString(); } - private string processGlobal(string expr) + private string ProcessGlobal(string expr) { var val = PluginMain.debugManager.FlashInterface.Session.getGlobal(expr); //var val = PluginMain.debugManager.FlashInterface.Session.getValue(Convert.ToInt64(expr)); @@ -146,27 +250,98 @@ private string processGlobal(string expr) return ctx.FormatValue(val); } - private void clearAllToolStripMenuItem_Click(object sender, EventArgs e) + private void ClearAllToolStripMenuItem_Click(object sender, EventArgs e) { this.textBox.Clear(); this.history.Clear(); this.historyPos = 0; } - private void cutToolStripMenuItem_Click(object sender, EventArgs e) + private void CutToolStripMenuItem_Click(object sender, EventArgs e) { this.textBox.Cut(); } - private void copyToolStripMenuItem_Click(object sender, EventArgs e) + private void CopyToolStripMenuItem_Click(object sender, EventArgs e) { this.textBox.Copy(); } - private void pasteToolStripMenuItem_Click(object sender, EventArgs e) + private void PasteToolStripMenuItem_Click(object sender, EventArgs e) { this.textBox.Paste(); } + #region ASCompletionListBackend.BackendFileInfo Methods + + PluginCore.Helpers.EncodingFileInfo ASCompletionListBackend.IBackendFileGetter.GetFileContent(ASCompletionListBackend.BackendFileInfo file) + { + var debugger = PluginMain.debugManager.FlashInterface; + if (!debugger.isDebuggerStarted || !debugger.isDebuggerSuspended || PluginMain.debugManager.CurrentFrame >= debugger.GetFrames().Length) + return null; + var location = debugger.GetFrames()[PluginMain.debugManager.CurrentFrame].getLocation(); + + // Could somebody want to pass a file pointing to a file different to the one being debugged? highly unlikely + + var sourceFile = location.getFile(); + var sourceFileText = new StringBuilder(); + if (sourceFile != null) + { + for (int i = 1, count = sourceFile.getLineCount(); i <= count; i++) + { + sourceFileText.Append(sourceFile.getLine(i).ToString()).Append(PluginCore.Utilities.LineEndDetector.GetNewLineMarker((int)PluginBase.Settings.EOLMode)); + } + } + + if (sourceFileText.Length == 0) + { + if (file.File != null && System.IO.File.Exists(file.File) && + file.File == PluginMain.debugManager.GetLocalPath(sourceFile)) + { + // Notify the user of this case? + //MessageBox.Show("Source code no available, but potential matching file found on disk, do you want to use it?"); + return PluginCore.Helpers.FileHelper.GetEncodingFileInfo(file.File); + } + + return null; + } + + // Maybe we should convert from UTF-16 to UTF-8? no problems so far + return new PluginCore.Helpers.EncodingFileInfo { CodePage = Encoding.UTF8.CodePage, Contents = sourceFileText.ToString() }; + } + + bool ASCompletionListBackend.IBackendFileGetter.GetFileInfo(out ASCompletionListBackend.BackendFileInfo file) + { + file = default(ASCompletionListBackend.BackendFileInfo); + var debugger = PluginMain.debugManager.FlashInterface; + if (!debugger.isDebuggerStarted || !debugger.isDebuggerSuspended || PluginMain.debugManager.CurrentFrame >= debugger.GetFrames().Length) + return false; + var location = debugger.GetFrames()[PluginMain.debugManager.CurrentFrame].getLocation(); + file.File = PluginMain.debugManager.GetLocalPath(location.getFile()); + if (file.File == null && (location.getFile() == null || location.getFile().getLineCount() == 0)) + return false; + + file.Line = location.getLine() - 1; + + return true; + } + + string ASCompletionListBackend.IBackendFileGetter.GetExpression() + { + int curLine = this.textBox.GetLineFromCharIndex(this.textBox.SelectionStart); + string line = (curLine < this.textBox.Lines.Length) ? this.textBox.Lines[curLine] : ""; + + if (curLine != textBox.Lines.Length - 1 || !line.StartsWith("p ")) + return null; + + int lineLength = textBox.SelectionStart - textBox.GetFirstCharIndexFromLine(textBox.Lines.Length - 1) - 2; + if (lineLength < 0) + return null; + + return line.Substring(2, lineLength); + } + + #endregion + } } diff --git a/External/Plugins/FlashDebugger/FlashDebugger.csproj b/External/Plugins/FlashDebugger/FlashDebugger.csproj index fb47051fe4..8573387a6d 100644 --- a/External/Plugins/FlashDebugger/FlashDebugger.csproj +++ b/External/Plugins/FlashDebugger/FlashDebugger.csproj @@ -96,9 +96,15 @@ + + Component + + + Component + diff --git a/External/Plugins/HaXeContext/Completion/HaxeComplete.cs b/External/Plugins/HaXeContext/Completion/HaxeComplete.cs index 6ee47bba65..bde4b48365 100644 --- a/External/Plugins/HaXeContext/Completion/HaxeComplete.cs +++ b/External/Plugins/HaXeContext/Completion/HaxeComplete.cs @@ -55,7 +55,7 @@ public HaxeComplete(ScintillaControl sci, ASExpr expr, bool autoHide, IHaxeCompl handler = completionHandler; CompilerService = compilerService; Status = HaxeCompleteStatus.NONE; - FileName = PluginBase.MainForm.CurrentDocument.FileName; + FileName = sci.FileName; } /* EXECUTION */ diff --git a/External/Plugins/XMLCompletion/XMLComplete.cs b/External/Plugins/XMLCompletion/XMLComplete.cs index 0a3330f0c0..1dd39c7201 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 c699651405..263dc69c4a 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 936a04504a..ba65fbfbe9 100644 --- a/FlashDevelop/MainForm.cs +++ b/FlashDevelop/MainForm.cs @@ -1522,13 +1522,15 @@ 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 return Win32.SendMessage(hWnd, m.Msg, m.WParam, m.LParam) != IntPtr.Zero; } } } diff --git a/FlashDevelop/Managers/ScintillaManager.cs b/FlashDevelop/Managers/ScintillaManager.cs index a2f839d99b..75cf8ff83c 100644 --- a/FlashDevelop/Managers/ScintillaManager.cs +++ b/FlashDevelop/Managers/ScintillaManager.cs @@ -212,6 +212,7 @@ public static void ApplySciSettings(ScintillaControl sci, Boolean hardUpdate) sci.TabWidth = Globals.Settings.TabWidth; sci.ViewWS = Convert.ToInt32(Globals.Settings.ViewWhitespace); sci.WrapMode = Convert.ToInt32(Globals.Settings.WrapText); + sci.CamelHumps = Globals.Settings.UseCamelHumps; sci.SetProperty("fold", Convert.ToInt32(Globals.Settings.UseFolding).ToString()); sci.SetProperty("fold.comment", Convert.ToInt32(Globals.Settings.FoldComment).ToString()); sci.SetProperty("fold.compact", Convert.ToInt32(Globals.Settings.FoldCompact).ToString()); diff --git a/FlashDevelop/Settings/Accessors.cs b/FlashDevelop/Settings/Accessors.cs index 6f1823f4be..d33ffb8188 100644 --- a/FlashDevelop/Settings/Accessors.cs +++ b/FlashDevelop/Settings/Accessors.cs @@ -341,6 +341,12 @@ public Int32 HighlightMatchingWordsDelay set { this.highlightMatchingWordsDelay = value; } } + [DefaultValue(false)] + [DisplayName("Use Camel Humps")] + [LocalizedCategory("FlashDevelop.Category.Editor")] + [LocalizedDescription("FlashDevelop.Description.UseCamelHumps")] + public Boolean UseCamelHumps { get; set; } + #endregion #region Locale @@ -900,7 +906,7 @@ public Int64 LastUpdateCheck { get { return this.lastUpdateCheck; } set { this.lastUpdateCheck = value; } - } + } #endregion diff --git a/PluginCore/PluginCore.csproj b/PluginCore/PluginCore.csproj index 4239c60020..4d6d53df83 100644 --- a/PluginCore/PluginCore.csproj +++ b/PluginCore/PluginCore.csproj @@ -309,6 +309,11 @@ Component + + + + Form + Component @@ -317,6 +322,9 @@ Form + + Component + Component diff --git a/PluginCore/PluginCore/Controls/CompletionList.cs b/PluginCore/PluginCore/Controls/CompletionList.cs index 4d8cae7d44..407e98f4b9 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,25 +120,7 @@ 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.EndsWith(pname)) found = item; - } - if (found != null) - { - defaultItem = found; - completionList.SelectedItem = found; - } + completionList.SelectItem(name); } /// @@ -240,76 +128,20 @@ static public void SelectItem(String name) /// 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.StartsWith(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.IndexOf(word); - if (p2 >= 0) - { - int p3 = label.LastIndexOf("." + word); // in qualified type name - if (p3 > 0) - { - if (p3 == label.LastIndexOf('.')) - { - if (label.EndsWith("." + 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.IndexOf("." + 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.IndexOf(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.EndsWith("." + 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.IndexOf(tail) >= word.Length) - { - replace = replace.Substring(0, replace.IndexOf(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..4f0c0bff95 --- /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.StartsWith(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.IndexOf(word); + if (p2 >= 0) + { + int p3 = label.LastIndexOf("." + word); // in qualified type name + if (p3 > 0) + { + if (p3 == label.LastIndexOf('.')) + { + if (label.EndsWith("." + 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.IndexOf("." + 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.IndexOf(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.EndsWith("." + 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.IndexOf(tail) >= word.Length) + { + replace = replace.Substring(0, replace.IndexOf(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 a1ca07b68e..e55fcfc11d 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; namespace PluginCore.Controls { @@ -13,67 +13,105 @@ 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.Location = new Point(2,1); + toolTipRTB = new SelectableRichTextBox(); + toolTipRTB.Location = new Point(2, 1); toolTipRTB.BackColor = SystemColors.Info; toolTipRTB.ForeColor = SystemColors.InfoText; toolTipRTB.BorderStyle = BorderStyle.None; @@ -83,14 +121,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) @@ -105,7 +143,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() @@ -123,9 +177,10 @@ public bool AutoSize(int availableWidth, int maxWidth) Size txtSize = WinFormUtils.MeasureRichTextBox(toolTipRTB, false, toolTipRTB.Width, toolTipRTB.Height, false); // tooltip larger than the window: wrap - int limitLeft = ((Form)PluginBase.MainForm).ClientRectangle.Left + 10; - int limitRight = ((Form)PluginBase.MainForm).ClientRectangle.Right - 10; - int limitBottom = ((Form)PluginBase.MainForm).ClientRectangle.Bottom - 26; + var screenArea = Screen.FromControl(owner.Owner).WorkingArea; + int limitLeft = screenArea.Left + 1; + int limitRight = screenArea.Right - 1; + int limitBottom = screenArea.Bottom - 26; // int maxW = availableWidth > 0 ? availableWidth : limitRight - limitLeft; if (maxW > maxWidth && maxWidth > 0) @@ -149,10 +204,10 @@ public bool AutoSize(int availableWidth, int maxWidth) int h = txtSize.Height + 2; int dh = 1; int dw = 2; - if (h > (limitBottom - toolTip.Top)) + if (h > (limitBottom - host.Top)) { w += 15; - h = limitBottom - toolTip.Top; + h = limitBottom - host.Top; dh = 4; dw = 5; @@ -160,13 +215,13 @@ public bool AutoSize(int availableWidth, int maxWidth) } 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; @@ -178,50 +233,67 @@ public void ShowAtMouseLocation(string text) { if (text != Text) { - toolTip.Visible = false; + host.Visible = false; Text = text; } ShowAtMouseLocation(); } - + public void ShowAtMouseLocation() { //ITabbedDocument doc = PluginBase.MainForm.CurrentDocument; - mousePos = ((Form)PluginBase.MainForm).PointToClient(Control.MousePosition); - toolTip.Left = mousePos.X;// +sci.Left; - 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 - 10;// +sci.Top; - toolTip.Show(); - toolTip.BringToFront(); + host.Top = mousePos.Y - host.Height - 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(); } @@ -247,15 +319,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); } @@ -264,8 +336,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/TextBoxEx.cs b/PluginCore/PluginCore/Controls/TextBoxEx.cs new file mode 100644 index 0000000000..0544e093b3 --- /dev/null +++ b/PluginCore/PluginCore/Controls/TextBoxEx.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PluginCore.Controls +{ + public class TextBoxEx : TextBox + { + private const int WM_SYSKEYDOWN = 0x0104; + + public event KeyEventHandler KeyPosted; //Hacky event for MethodCallTip, although with some rather valid use cases + public event ScrollEventHandler Scroll; + + protected override void DefWndProc(ref Message m) + { + base.DefWndProc(ref m); + + if (m.Msg == Win32.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 Message m) + { + switch (m.Msg) + { + case Win32.WM_HSCROLL: + case Win32.WM_VSCROLL: + case Win32.WM_MOUSEWHEEL: + WmScroll(ref m); + break; + default: + base.WndProc(ref m); + break; + } + + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + protected virtual void OnScroll(ScrollEventArgs e) + { + if (Scroll != null) + Scroll(this, e); + } + + protected virtual void OnKeyPosted(KeyEventArgs e) + { + if (KeyPosted != null) + KeyPosted(this, e); + } + + private void WmScroll(ref Message m) + { + ScrollOrientation so; + ScrollEventType set = (ScrollEventType)((short)((int)(long)m.WParam & 0xffff)); + + // We're not interested in the actual scroll change right now + if (m.Msg == Win32.WM_HSCROLL) + { + so = ScrollOrientation.HorizontalScroll; + base.WndProc(ref m); + } + else + { + so = ScrollOrientation.VerticalScroll; + base.WndProc(ref m); + } + + OnScroll(new ScrollEventArgs(set, 0, 0, so)); + } + + } + + public class TextBoxTarget : ICompletionListHost + { + + #region ICompletionListTarget Members + + public event EventHandler LostFocus + { + add { _owner.LostFocus += value; } + remove { _owner.LostFocus -= value; } + } + + private EventHandler positionChanged; + public event EventHandler PositionChanged + { + add + { + if (positionChanged == null || positionChanged.GetInvocationList().Length == 0) + { + _owner.Scroll += Owner_Scroll; + BuildControlHierarchy(_owner); + } + positionChanged += value; + } + remove + { + positionChanged -= value; + if (positionChanged == null || positionChanged.GetInvocationList().Length < 1) + { + _owner.Scroll -= Owner_Scroll; + ClearControlHierarchy(); + } + } + } + + public event EventHandler SizeChanged + { + add { Owner.SizeChanged += value; } + remove { Owner.SizeChanged -= value; } + } + + public event KeyEventHandler KeyDown + { + add { _owner.KeyDown += value; } + remove { _owner.KeyDown -= value; } + } + + public event KeyEventHandler KeyPosted + { + add { _owner.KeyPosted += value; } + remove { _owner.KeyPosted -= value; } + } + + public event KeyPressEventHandler KeyPress + { + add { _owner.KeyPress += value; } + remove { _owner.KeyPress -= value; } + } + + public event MouseEventHandler MouseDown + { + add { _owner.MouseDown += value; } + remove { _owner.MouseDown -= value; } + } + + private TextBoxEx _owner; + public Control Owner + { + get { return _owner; } + } + + public string SelectedText + { + get { return _owner.SelectedText; } + set { _owner.SelectedText = value; } + } + + public int SelectionEnd + { + get { return _owner.SelectionStart + _owner.SelectionLength; } + set { _owner.SelectionLength = value - _owner.SelectionStart; } + } + + public int SelectionStart + { + get { return _owner.SelectionStart; } + set { _owner.SelectionStart = value; } + } + + public int CurrentPos + { + get { return _owner.SelectionStart; } + } + + public bool IsEditable + { + get { return !_owner.ReadOnly; } + } + + public TextBoxTarget(TextBoxEx owner) + { + _owner = owner; + } + + public int GetLineFromCharIndex(int pos) + { + return _owner.GetLineFromCharIndex(pos); + } + + public Point GetPositionFromCharIndex(int pos) + { + return _owner.GetPositionFromCharIndex(pos == _owner.TextLength ? pos - 1 : pos); + } + + public int GetLineHeight() + { + using (Graphics g = _owner.CreateGraphics()) + { + SizeF textSize = g.MeasureString("S", _owner.Font); + return (int)Math.Ceiling(textSize.Height); + } + } + + public void SetSelection(int start, int end) + { + _owner.SelectionStart = start; + _owner.SelectionLength = end - start; + } + + public void BeginUndoAction() + { + // TODO + } + + public void EndUndoAction() + { + // TODO + } + + #endregion + + private List controlHierarchy = new List(); + + private void BuildControlHierarchy(Control current) + { + while (current != null) + { + current.LocationChanged += Control_LocationChanged; + current.ParentChanged += Control_ParentChanged; + controlHierarchy.Add(current); + current = current.Parent; + } + } + + private void ClearControlHierarchy() + { + foreach (var control in controlHierarchy) + { + control.LocationChanged -= Control_LocationChanged; + control.ParentChanged -= Control_ParentChanged; + } + controlHierarchy.Clear(); + } + + private void Control_LocationChanged(object sender, EventArgs e) + { + if (positionChanged != null) + positionChanged(sender, e); + } + + private void Control_ParentChanged(object sender, EventArgs e) + { + ClearControlHierarchy(); + BuildControlHierarchy(_owner); + if (positionChanged != null) + positionChanged(sender, e); + } + + private void Owner_Scroll(object sender, ScrollEventArgs e) + { + if (positionChanged != null) + positionChanged(sender, e); + } + + } + +} diff --git a/PluginCore/PluginCore/Controls/UITools.cs b/PluginCore/PluginCore/Controls/UITools.cs index 0567478e0b..f71fe5606e 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) @@ -158,6 +153,7 @@ public void ListenTo(ScintillaControl sci) sci.UpdateUI += new UpdateUIHandler(OnUIRefresh); sci.TextInserted += new TextInsertedHandler(OnTextInserted); sci.TextDeleted += new TextDeletedHandler(OnTextDeleted); + sci.GotFocus += OnGotFocus; } /// @@ -184,6 +180,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) @@ -221,7 +218,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); } @@ -231,16 +231,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; } @@ -249,7 +247,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) @@ -294,8 +292,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(); } @@ -310,6 +308,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; @@ -318,24 +325,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) @@ -346,76 +352,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.EndsWith(", Shift") && ks.IndexOf(',') == 1) || ks.StartsWith("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/PluginCore/Interfaces.cs b/PluginCore/PluginCore/Interfaces.cs index fdc6e2d727..a07875c579 100644 --- a/PluginCore/PluginCore/Interfaces.cs +++ b/PluginCore/PluginCore/Interfaces.cs @@ -512,6 +512,7 @@ public interface ISettings Boolean DisableSmartMatch { get; set; } Boolean SaveUnicodeWithBOM { get; set; } String InsertionTriggers { get; set; } + Boolean UseCamelHumps { get; set; } #endregion } diff --git a/PluginCore/PluginCore/Resources/de_DE.resX b/PluginCore/PluginCore/Resources/de_DE.resX index b46bd194a3..a7bc409e5e 100644 --- a/PluginCore/PluginCore/Resources/de_DE.resX +++ b/PluginCore/PluginCore/Resources/de_DE.resX @@ -3171,6 +3171,10 @@ Bitte bedenken Sie, dass Sie viele Projektoptionen nicht mehr nutzen können, so Wann Wörter die der momentanen Auswahl entsprechen hervorgehoben werden sollen. Added after 4.7.0 + + If true, Next/Previous Word related actions work on word segments delimited by capitalisation changes or underscores. + Added after 4.7.1 + Aufgaben in ausgeschlossenen Pfaden werden nicht angezeigt. diff --git a/PluginCore/PluginCore/Resources/en_US.resX b/PluginCore/PluginCore/Resources/en_US.resX index 365abd1eef..f94af995a3 100644 --- a/PluginCore/PluginCore/Resources/en_US.resX +++ b/PluginCore/PluginCore/Resources/en_US.resX @@ -3176,6 +3176,10 @@ Please note that with injection enabled, you will not be able to use many projec When words matching the current selection should be highlighted. Added after 4.7.0 + + If true, Next/Previous Word related actions work on word segments delimited by capitalisation changes or underscores. + Added after 4.7.1 + Tasks under the excluded paths are not shown in the task list. diff --git a/PluginCore/PluginCore/Resources/eu_ES.resX b/PluginCore/PluginCore/Resources/eu_ES.resX index c3b4b84a53..4758cc799a 100644 --- a/PluginCore/PluginCore/Resources/eu_ES.resX +++ b/PluginCore/PluginCore/Resources/eu_ES.resX @@ -3164,6 +3164,10 @@ Kontutan izan injekzioa gaituz gero, ezin izanen diren proiektuaren hainbat auke Hitzak bat datozenean, uneko aukeraketa nabarmendu. Added after 4.7.0 + + If true, Next/Previous Word related actions work on word segments delimited by capitalisation changes or underscores. + Added after 4.7.1 + Baztertutako bideetako zereginak ez dira zereginen zerrendan erakutisko. diff --git a/PluginCore/PluginCore/Resources/ja_JP.resX b/PluginCore/PluginCore/Resources/ja_JP.resX index f2c9f995dc..b9516d41f2 100644 --- a/PluginCore/PluginCore/Resources/ja_JP.resX +++ b/PluginCore/PluginCore/Resources/ja_JP.resX @@ -3166,6 +3166,10 @@ AfterSimilarAccessorMethod: 同様のアクセサメソッドの後に配置。" When words matching the current selection should be highlighted. Added after 4.7.0 + + If true, Next/Previous Word related actions work on word segments delimited by capitalisation changes or underscores. + Added after 4.7.1 + FlashDevelop のウインドウにシステムカラーを適応します。 diff --git a/PluginCore/PluginCore/Resources/zh_CN.resx b/PluginCore/PluginCore/Resources/zh_CN.resx index e5ac389641..6bfc0c19e7 100644 --- a/PluginCore/PluginCore/Resources/zh_CN.resx +++ b/PluginCore/PluginCore/Resources/zh_CN.resx @@ -3171,6 +3171,10 @@ When words matching the current selection should be highlighted. Added after 4.7.0 + + If true, Next/Previous Word related actions work on word segments delimited by capitalisation changes or underscores. + Added after 4.7.1 + 排除路径下的任务不显示在任务列表中。 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 c92ecda91d..24b8064edd 100644 --- a/PluginCore/ScintillaNet/ScintillaControl.cs +++ b/PluginCore/ScintillaNet/ScintillaControl.cs @@ -2,7 +2,6 @@ using System.IO; using System.Text; using System.Drawing; -using System.Collections; using System.Windows.Forms; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -20,9 +19,9 @@ namespace ScintillaNet public class ScintillaControl : Control, IEventHandler { private bool saveBOM; + private bool camelHumps; private Encoding encoding; - private int directPointer; - private IntPtr hwndScintilla; + private IntPtr directPointer; private bool hasHighlights = false; private bool ignoreAllKeys = false; private bool isBraceMatching = true; @@ -33,7 +32,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; @@ -179,8 +179,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); @@ -188,7 +188,7 @@ private void AddScrollBars(ScintillaControl sender) sender.Painted += sender.OnScrollUpdate; sender.IsVScrollBar = vScroll; sender.IsHScrollBar = hScroll; - sender.OnResize(null, null); + sender.OnResize2(null, null); } /// @@ -198,8 +198,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); @@ -207,7 +207,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 @@ -223,19 +223,45 @@ 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()) { LoadLibrary(fullpath); - hwndScintilla = CreateWindowEx(0, "Scintilla", "", WS_CHILD_VISIBLE_TABSTOP, 0, 0, this.Width, this.Height, this.Handle, 0, new IntPtr(0), null); - directPointer = (int)SlowPerform(2185, 0, 0); - 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) @@ -251,11 +277,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 @@ -264,7 +302,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; @@ -303,19 +340,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 /// @@ -1747,11 +1778,13 @@ public int DirectFunction /// Retrieve a pointer value to use as the first argument when calling /// the function returned by GetDirectFunction. /// - public int DirectPointer + public IntPtr DirectPointer { get { - return (int)SPerform(2185, 0, 0); + if (directPointer == IntPtr.Zero) + directPointer = SendMessage(Handle, 2185, 0, 0); + return directPointer; } } @@ -2399,7 +2432,7 @@ public bool ScrollWidthTracking /// public virtual void AddIgnoredKeys(Keys keys) { - ignoredKeys.Add((int)keys, (int)keys); + ignoredKeys.Add((int)keys); } /// @@ -2423,15 +2456,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); } /// @@ -4198,6 +4223,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. /// @@ -4251,7 +4298,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)); } /// @@ -4259,7 +4315,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)); } /// @@ -5151,10 +5216,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; @@ -5162,6 +5233,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 @@ -5249,10 +5330,7 @@ public ShortcutOverride(Keys keys, Action action) public static extern int GetDeviceCaps(IntPtr hdc, Int32 capindex); [DllImport("user32.dll")] - public static extern int SendMessage(int 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); @@ -5264,18 +5342,43 @@ public ShortcutOverride(Keys keys, Action action) public static extern void DragAcceptFiles(IntPtr hwnd, int accept); [DllImport("scilexer.dll", EntryPoint = "Scintilla_DirectFunction")] - public static extern int Perform(int directPointer, UInt32 message, UInt32 wParam, UInt32 lParam); + public static extern int Perform(IntPtr directPointer, UInt32 message, UInt32 wParam, UInt32 lParam); - public UInt32 SlowPerform(UInt32 message, UInt32 wParam, UInt32 lParam) - { - return (UInt32)SendMessage((int)hwndScintilla, message, (int)wParam, (int)lParam); - } public UInt32 SPerform(UInt32 message, UInt32 wParam, UInt32 lParam) { - if (Win32.ShouldUseWin32()) return (UInt32)Perform(directPointer, message, wParam, lParam); + if (Win32.ShouldUseWin32()) return (UInt32)Perform(DirectPointer, message, wParam, lParam); else return (UInt32)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) @@ -5283,10 +5386,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; @@ -5310,195 +5416,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; } } @@ -5931,6 +6082,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 /// @@ -7094,6 +7275,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 = (int)SPerform(2153, (uint)line, 0); + byte[] buffer = new byte[sz + 1]; + unsafe + { + fixed (byte* b = buffer) SPerform(2153, (uint)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 = (int)SPerform(2153, (uint)line, 0); + byte[] buffer = new byte[sz + 1]; + unsafe + { + fixed (byte* b = buffer) SPerform(2153, (uint)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 }