diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj index f46591e5c8..cf40f49d34 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj @@ -103,6 +103,7 @@ + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonCodeCompletionBinding.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonCodeCompletionBinding.cs index 55dd92c2ac..413947328a 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonCodeCompletionBinding.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonCodeCompletionBinding.cs @@ -12,6 +12,11 @@ namespace ICSharpCode.PythonBinding { public class PythonCodeCompletionBinding : DefaultCodeCompletionBinding { + public PythonCodeCompletionBinding() + { + base.insightHandler = new PythonInsightWindowHandler(); + } + /// /// Shows the code completion window if the keyword is handled. /// diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonInsightWindowHandler.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonInsightWindowHandler.cs new file mode 100644 index 0000000000..62689cc1e4 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonInsightWindowHandler.cs @@ -0,0 +1,77 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.CodeCompletion; + +namespace ICSharpCode.PythonBinding +{ + public class PythonInsightWindowHandler : IInsightWindowHandler + { + ITextEditor editor; + IInsightWindow insightWindow; + + public void InitializeOpenedInsightWindow(ITextEditor editor, IInsightWindow insightWindow) + { + this.editor = editor; + this.insightWindow = insightWindow; + int offset = insightWindow.StartOffset; + insightWindow.DocumentChanged += DocumentChanged; + } + + void DocumentChanged(object sender, TextChangeEventArgs e) + { + if (IsOutsideMethodCall()) { + insightWindow.Close(); + } + } + + bool IsOutsideMethodCall() + { + string text = GetTextInsideMethodCallUpToCursor(); + return TextContainsClosingBracketForMethod(text); + } + + string GetTextInsideMethodCallUpToCursor() + { + int insightStartOffset = insightWindow.StartOffset; + int currentOffset = editor.Caret.Offset; + int length = currentOffset - insightStartOffset; + if (length < 0) { + // Force completion window to close by returning the close bracket. + return ")"; + } + return editor.Document.GetText(insightStartOffset, length); + } + + bool TextContainsClosingBracketForMethod(string text) + { + int bracketCount = 1; + foreach (char ch in text) { + switch (ch) { + case '(': + bracketCount++; + break; + case ')': + bracketCount--; + if (bracketCount == 0) { + return true; + } + break; + } + } + return false; + } + + public bool InsightRefreshOnComma(ITextEditor editor, char ch, out IInsightWindow insightWindow) + { + insightWindow = null; + return false; + } + + public void HighlightParameter(IInsightWindow window, int index) + { + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonCodeCompletionBindingTests.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonCodeCompletionBindingTests.cs new file mode 100644 index 0000000000..5f6725677b --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonCodeCompletionBindingTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.PythonBinding; +using NUnit.Framework; +using PythonBinding.Tests.Utils; + +namespace PythonBinding.Tests.Completion +{ + [TestFixture] + public class PythonCodeCompletionBindingTests + { + TestablePythonCodeCompletionBinding completionBinding; + + void CreatePythonCodeCompletionBinding() + { + completionBinding = new TestablePythonCodeCompletionBinding(); + } + + [Test] + public void InsightHandler_CreateNewCodecompletionBindingInstance_IsSetToPythonInsightWindowHandlerInstance() + { + CreatePythonCodeCompletionBinding(); + PythonInsightWindowHandler handler = completionBinding.PythonInsightWindowHandler; + Assert.IsNotNull(handler); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonInsightWindowHandlerTests.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonInsightWindowHandlerTests.cs new file mode 100644 index 0000000000..c920141ab0 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Completion/PythonInsightWindowHandlerTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.PythonBinding; +using ICSharpCode.Scripting.Tests.Utils; +using ICSharpCode.SharpDevelop.Editor; +using NUnit.Framework; +using PythonBinding.Tests.Utils; + +namespace PythonBinding.Tests.Completion +{ + [TestFixture] + public class PythonInsightWindowHandlerTests + { + PythonInsightWindowHandler handler; + MockTextEditor fakeTextEditor; + FakeInsightWindow fakeInsightWindow; + + void CreatePythonInsightWindowHandler() + { + fakeTextEditor = new MockTextEditor(); + fakeInsightWindow = new FakeInsightWindow(); + handler = new PythonInsightWindowHandler(); + } + + void InitializePythonInsightWindowHandler() + { + handler.InitializeOpenedInsightWindow(fakeTextEditor, fakeInsightWindow); + } + + TextChangeEventArgs CreateInsertedTextChangeEventArgs(int offset, string insertedText) + { + return new TextChangeEventArgs(offset, String.Empty, insertedText); + } + + [Test] + public void InitializeOpenedInsightWindow_CloseParenthesisCharacterAddedToDocument_InsightWindowClosed() + { + CreatePythonInsightWindowHandler(); + fakeTextEditor.FakeDocument.Text = "method("; + fakeTextEditor.FakeCaret.Offset = 7; + fakeInsightWindow.StartOffset = 7; + InitializePythonInsightWindowHandler(); + + int newCaretOffset = 8; + fakeTextEditor.FakeCaret.Offset = newCaretOffset; + fakeTextEditor.FakeDocument.Text = "method()"; + TextChangeEventArgs e = CreateInsertedTextChangeEventArgs(newCaretOffset, ")"); + fakeInsightWindow.FireDocumentChangedEvent(e); + + bool closed = fakeInsightWindow.IsClosed; + Assert.IsTrue(closed); + } + + [Test] + public void InitializeOpenedInsightWindow_MethodCallWithCursorAtOpenBracket_InsightWindowIsClosedBeforeDocumentIsChanged() + { + CreatePythonInsightWindowHandler(); + fakeTextEditor.FakeDocument.Text = "method("; + fakeTextEditor.FakeCaret.Offset = 7; + fakeInsightWindow.StartOffset = 7; + InitializePythonInsightWindowHandler(); + + bool closed = fakeInsightWindow.IsClosed; + Assert.IsFalse(closed); + } + + [Test] + public void InitializeOpenedInsightWindow_SingleCharacterAddedToDocumentAfterOpenParenthesis_InsightWindowIsNotClosed() + { + CreatePythonInsightWindowHandler(); + fakeTextEditor.FakeDocument.Text = "method("; + fakeTextEditor.FakeCaret.Offset = 7; + fakeInsightWindow.StartOffset = 7; + InitializePythonInsightWindowHandler(); + + int newCaretOffset = 8; + fakeTextEditor.FakeCaret.Offset = newCaretOffset; + fakeTextEditor.FakeDocument.Text = "method(a"; + TextChangeEventArgs e = CreateInsertedTextChangeEventArgs(newCaretOffset, "a"); + fakeInsightWindow.FireDocumentChangedEvent(e); + + bool closed = fakeInsightWindow.IsClosed; + Assert.IsFalse(closed); + } + + [Test] + public void InitializeOpenedInsightWindow_MethodCallInsideMethodCallAndCloseParenthesisCharacterAddedToDocument_InsightWindowIsNotClosed() + { + CreatePythonInsightWindowHandler(); + fakeTextEditor.FakeDocument.Text = "method(a("; + fakeTextEditor.FakeCaret.Offset = 9; + fakeInsightWindow.StartOffset = 7; + InitializePythonInsightWindowHandler(); + + int newCaretOffset = 10; + fakeTextEditor.FakeCaret.Offset = newCaretOffset; + fakeTextEditor.FakeDocument.Text = "method(a()"; + TextChangeEventArgs e = CreateInsertedTextChangeEventArgs(newCaretOffset, ")"); + fakeInsightWindow.FireDocumentChangedEvent(e); + + bool closed = fakeInsightWindow.IsClosed; + Assert.IsFalse(closed); + } + + [Test] + public void InitializeOpenedInsightWindow_CharacterAddedToDocumentBeforeStartOfInsightWindow_InsightWindowClosed() + { + CreatePythonInsightWindowHandler(); + fakeTextEditor.FakeDocument.Text = "method("; + fakeTextEditor.FakeCaret.Offset = 7; + fakeInsightWindow.StartOffset = 7; + InitializePythonInsightWindowHandler(); + + int newCaretOffset = 1; + fakeTextEditor.FakeCaret.Offset = newCaretOffset; + fakeTextEditor.FakeDocument.Text = "aethod("; + TextChangeEventArgs e = CreateInsertedTextChangeEventArgs(newCaretOffset, "a"); + fakeInsightWindow.FireDocumentChangedEvent(e); + + bool closed = fakeInsightWindow.IsClosed; + Assert.IsTrue(closed); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj index c887f2fc3a..308b86f889 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj @@ -97,9 +97,11 @@ + + @@ -417,6 +419,7 @@ + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/FakeInsightWindow.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/FakeInsightWindow.cs new file mode 100644 index 0000000000..3f13c700ae --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/FakeInsightWindow.cs @@ -0,0 +1,87 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.CodeCompletion; + +namespace PythonBinding.Tests.Utils +{ + public class FakeInsightWindow : IInsightWindow + { + #pragma warning disable 67 + public event EventHandler DocumentChanged; + public event EventHandler SelectedItemChanged; + public event EventHandler CaretPositionChanged; + public event EventHandler Closed; + #pragma warning restore 67 + + public bool IsClosed; + + public IList Items { + get { + throw new NotImplementedException(); + } + } + + public IInsightItem SelectedItem { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + public double Width { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + public double Height { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + public bool CloseAutomatically { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + public int StartOffset { get; set; } + + public int EndOffset { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + public void Close() + { + IsClosed = true; + } + + public void FireDocumentChangedEvent(TextChangeEventArgs e) + { + if (DocumentChanged != null) { + DocumentChanged(this, e); + } + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/TestablePythonCodeCompletionBinding.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/TestablePythonCodeCompletionBinding.cs index db0e56fb4a..c1e140899b 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/TestablePythonCodeCompletionBinding.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/TestablePythonCodeCompletionBinding.cs @@ -47,5 +47,9 @@ namespace PythonBinding.Tests.Utils IsCodeCompletionWindowDisplayed = true; CompletionItemProviderUsedWhenDisplayingCodeCompletionWindow = completionItemProvider; } + + public PythonInsightWindowHandler PythonInsightWindowHandler { + get { return base.insightHandler as PythonInsightWindowHandler; } + } } } diff --git a/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeCaret.cs b/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeCaret.cs index 0d50e42fc8..d0e1c694ad 100644 --- a/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeCaret.cs +++ b/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeCaret.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using ICSharpCode.NRefactory; using ICSharpCode.SharpDevelop.Editor; namespace ICSharpCode.Scripting.Tests.Utils @@ -17,15 +18,7 @@ namespace ICSharpCode.Scripting.Tests.Utils } } - public int Offset { - get { - throw new NotImplementedException(); - } - set { - throw new NotImplementedException(); - } - } - + public int Offset { get; set; } public int Line { get; set; } public int Column { @@ -37,7 +30,7 @@ namespace ICSharpCode.Scripting.Tests.Utils } } - public ICSharpCode.NRefactory.Location Position { + public Location Position { get { throw new NotImplementedException(); } diff --git a/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeDocument.cs b/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeDocument.cs index 5d5d65dd59..a3adb7c6f3 100644 --- a/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeDocument.cs +++ b/src/AddIns/BackendBindings/Scripting/Test/Utils/FakeDocument.cs @@ -124,7 +124,7 @@ namespace ICSharpCode.Scripting.Tests.Utils public string GetText(int offset, int length) { - throw new NotImplementedException(); + return Text.Substring(offset, length); } public object GetService(Type serviceType)