You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
12 KiB
364 lines
12 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <owner name="Matthew Ward" email="mrward@users.sourceforge.net"/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
using ICSharpCode.Core; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Bookmarks; |
|
using ICSharpCode.SharpDevelop.DefaultEditor; |
|
using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.TextEditor; |
|
using ICSharpCode.TextEditor.Actions; |
|
using ICSharpCode.TextEditor.Document; |
|
using System; |
|
using System.ComponentModel; |
|
using System.Drawing; |
|
using System.Windows.Forms; |
|
|
|
namespace ICSharpCode.XmlEditor |
|
{ |
|
/// <summary> |
|
/// Xml editor derived from the SharpDevelop TextEditor control. |
|
/// </summary> |
|
public class XmlEditorControl : ICSharpCode.TextEditor.TextEditorControl |
|
{ |
|
static readonly string editActionsPath = "/AddIns/XmlEditor/EditActions"; |
|
static readonly string contextMenuPath = "/SharpDevelop/ViewContent/XmlEditor/ContextMenu"; |
|
CodeCompletionWindow codeCompletionWindow; |
|
XmlSchemaCompletionDataCollection schemaCompletionDataItems = new XmlSchemaCompletionDataCollection(); |
|
XmlSchemaCompletionData defaultSchemaCompletionData = null; |
|
string defaultNamespacePrefix = String.Empty; |
|
|
|
public XmlEditorControl() |
|
{ |
|
XmlFormattingStrategy strategy = new XmlFormattingStrategy(); |
|
Document.FormattingStrategy = (IFormattingStrategy)strategy; |
|
|
|
Document.HighlightingStrategy = HighlightingManager.Manager.FindHighlighter("XML"); |
|
Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy(); |
|
TextEditorProperties = new SharpDevelopTextEditorProperties(); |
|
|
|
Document.BookmarkManager.Factory = new SDBookmarkFactory(Document.BookmarkManager); |
|
Document.BookmarkManager.Added += new ICSharpCode.TextEditor.Document.BookmarkEventHandler(BookmarkAdded); |
|
Document.BookmarkManager.Removed += new ICSharpCode.TextEditor.Document.BookmarkEventHandler(BookmarkRemoved); |
|
|
|
GenerateEditActions(); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the schemas that the xml editor will use. |
|
/// </summary> |
|
/// <remarks> |
|
/// Probably should NOT have a 'set' property, but allowing one |
|
/// allows us to share the completion data amongst multiple |
|
/// xml editor controls. |
|
/// </remarks> |
|
public XmlSchemaCompletionDataCollection SchemaCompletionDataItems { |
|
get { |
|
return schemaCompletionDataItems; |
|
} |
|
set { |
|
schemaCompletionDataItems = value; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets or sets the default namespace prefix. |
|
/// </summary> |
|
public string DefaultNamespacePrefix { |
|
get { |
|
return defaultNamespacePrefix; |
|
} |
|
set { |
|
defaultNamespacePrefix = value; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets or sets the default schema completion data associated with this |
|
/// view. |
|
/// </summary> |
|
public XmlSchemaCompletionData DefaultSchemaCompletionData { |
|
get { |
|
return defaultSchemaCompletionData; |
|
} |
|
|
|
set { |
|
defaultSchemaCompletionData = value; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Called when the user hits Ctrl+Space. |
|
/// </summary> |
|
public void ShowCompletionWindow() |
|
{ |
|
if (!IsCaretAtDocumentStart) { |
|
// Find character before cursor. |
|
|
|
char ch = GetCharacterBeforeCaret(); |
|
|
|
HandleKeyPress(ch); |
|
} |
|
} |
|
|
|
protected override void InitializeTextAreaControl(TextAreaControl newControl) |
|
{ |
|
base.InitializeTextAreaControl(newControl); |
|
newControl.TextArea.KeyEventHandler += new ICSharpCode.TextEditor.KeyEventHandler(HandleKeyPress); |
|
|
|
newControl.ContextMenuStrip = MenuService.CreateContextMenu(this, contextMenuPath); |
|
newControl.SelectionManager.SelectionChanged += new EventHandler(SelectionChanged); |
|
newControl.Document.DocumentChanged += new DocumentEventHandler(DocumentChanged); |
|
newControl.Caret.PositionChanged += new EventHandler(CaretPositionChanged); |
|
newControl.TextArea.ClipboardHandler.CopyText += new CopyTextEventHandler(ClipboardHandlerCopyText); |
|
|
|
newControl.MouseWheel += new MouseEventHandler(TextAreaMouseWheel); |
|
newControl.DoHandleMousewheel = false; |
|
} |
|
|
|
/// <summary> |
|
/// Captures the user's key presses. |
|
/// </summary> |
|
/// <remarks> |
|
/// <para>The code completion window ProcessKeyEvent is not perfect |
|
/// when typing xml. If enter a space or ':' the text is |
|
/// autocompleted when it should not be.</para> |
|
/// <para>The code completion window has one predefined width, |
|
/// which cuts off any long namespaces that we show.</para> |
|
/// <para>The above issues have been resolved by duplicating |
|
/// the code completion window and fixing the problems in the |
|
/// duplicated class.</para> |
|
/// </remarks> |
|
protected bool HandleKeyPress(char ch) |
|
{ |
|
if (IsCodeCompletionWindowOpen) { |
|
if (codeCompletionWindow.ProcessKeyEvent(ch)) { |
|
return false; |
|
} |
|
} |
|
|
|
try { |
|
switch (ch) { |
|
case '<': |
|
case ' ': |
|
case '=': |
|
ShowCompletionWindow(ch); |
|
return false; |
|
default: |
|
if (XmlParser.IsAttributeValueChar(ch)) { |
|
if (IsInsideQuotes(ActiveTextAreaControl.TextArea)) { |
|
// Have to insert the character ourselves since |
|
// it is not actually inserted yet. If it is not |
|
// inserted now the code completion will not work |
|
// since the completion data provider attempts to |
|
// include the key typed as the pre-selected text. |
|
InsertCharacter(ch); |
|
ShowCompletionWindow(ch); |
|
return true; |
|
} |
|
} |
|
break; |
|
} |
|
} catch (Exception e) { |
|
MessageService.ShowError(e); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool IsCodeCompletionEnabled { |
|
get { |
|
return ICSharpCode.SharpDevelop.CodeCompletionOptions.EnableCodeCompletion; |
|
} |
|
} |
|
|
|
void CodeCompletionWindowClosed(object sender, EventArgs e) |
|
{ |
|
codeCompletionWindow.Closed -= new EventHandler(CodeCompletionWindowClosed); |
|
codeCompletionWindow.Dispose(); |
|
codeCompletionWindow = null; |
|
} |
|
|
|
bool IsCodeCompletionWindowOpen { |
|
get { |
|
return ((codeCompletionWindow != null) && (!codeCompletionWindow.IsDisposed)); |
|
} |
|
} |
|
|
|
void ShowCompletionWindow(char ch) |
|
{ |
|
if (IsCodeCompletionWindowOpen) { |
|
codeCompletionWindow.Close(); |
|
} |
|
|
|
if (IsCodeCompletionEnabled) { |
|
XmlCompletionDataProvider completionDataProvider = new XmlCompletionDataProvider(schemaCompletionDataItems, defaultSchemaCompletionData, defaultNamespacePrefix); |
|
codeCompletionWindow = CodeCompletionWindow.ShowCompletionWindow(ParentForm, this, FileName, completionDataProvider, ch, XmlEditorAddInOptions.ShowSchemaAnnotation); |
|
|
|
if (codeCompletionWindow != null) { |
|
codeCompletionWindow.Closed += new EventHandler(CodeCompletionWindowClosed); |
|
} |
|
} |
|
} |
|
|
|
void GenerateEditActions() |
|
{ |
|
IEditAction[] actions = (IEditAction[])(AddInTree.BuildItems(editActionsPath, this, false).ToArray(typeof(IEditAction))); |
|
|
|
foreach (IEditAction action in actions) { |
|
foreach (Keys key in action.Keys) { |
|
editactions[key] = action; |
|
} |
|
} |
|
} |
|
|
|
void DocumentChanged(object sender, DocumentEventArgs e) |
|
{ |
|
} |
|
|
|
void SelectionChanged(object sender, EventArgs e) |
|
{ |
|
} |
|
|
|
void ClipboardHandlerCopyText(object sender, CopyTextEventArgs e) |
|
{ |
|
SideBarView.PutInClipboardRing(e.Text); |
|
} |
|
|
|
void CaretPositionChanged(object sender, EventArgs e) |
|
{ |
|
StatusBarService.SetCaretPosition(ActiveTextAreaControl.TextArea.TextView.GetVisualColumn(ActiveTextAreaControl.Caret.Line, ActiveTextAreaControl.Caret.Column), ActiveTextAreaControl.Caret.Line, ActiveTextAreaControl.Caret.Column); |
|
} |
|
|
|
void TextAreaMouseWheel(object sender, MouseEventArgs e) |
|
{ |
|
TextAreaControl textAreaControl = (TextAreaControl)sender; |
|
|
|
if (IsCodeCompletionWindowOpen && codeCompletionWindow.Visible) { |
|
codeCompletionWindow.HandleMouseWheel(e); |
|
} else { |
|
textAreaControl.HandleMouseWheel(e); |
|
} |
|
} |
|
|
|
char GetCharacterBeforeCaret() |
|
{ |
|
string text = Document.GetText(ActiveTextAreaControl.TextArea.Caret.Offset - 1, 1); |
|
if (text.Length > 0) { |
|
return text[0]; |
|
} |
|
|
|
return '\0'; |
|
} |
|
|
|
bool IsCaretAtDocumentStart { |
|
get { |
|
return ActiveTextAreaControl.TextArea.Caret.Offset == 0; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Checks whether the caret is inside a set of quotes (" or '). |
|
/// </summary> |
|
bool IsInsideQuotes(TextArea textArea) |
|
{ |
|
bool inside = false; |
|
|
|
LineSegment line = textArea.Document.GetLineSegment(textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset)); |
|
if (line != null) { |
|
if ((line.Offset + line.Length > textArea.Caret.Offset) && |
|
(line.Offset < textArea.Caret.Offset)){ |
|
|
|
char charAfter = textArea.Document.GetCharAt(textArea.Caret.Offset); |
|
char charBefore = textArea.Document.GetCharAt(textArea.Caret.Offset - 1); |
|
|
|
if (((charBefore == '\'') && (charAfter == '\'')) || |
|
((charBefore == '\"') && (charAfter == '\"'))) { |
|
inside = true; |
|
} |
|
} |
|
} |
|
|
|
return inside; |
|
} |
|
|
|
/// <summary> |
|
/// Inserts a character into the text editor at the current offset. |
|
/// </summary> |
|
/// <remarks> |
|
/// This code is copied from the TextArea.SimulateKeyPress method. This |
|
/// code is needed to handle an issue with code completion. What if |
|
/// we want to include the character just typed as the pre-selected text |
|
/// for autocompletion? If we do not insert the character before |
|
/// displaying the autocompletion list we cannot set the pre-selected text |
|
/// because it is not actually inserted yet. The autocompletion window |
|
/// checks the offset of the pre-selected text and closes the window |
|
/// if the range is wrong. The offset check is always wrong since the text |
|
/// does not actually exist yet. The check occurs in |
|
/// CodeCompletionWindow.CaretOffsetChanged: |
|
/// <code>[[!CDATA[ int offset = control.ActiveTextAreaControl.Caret.Offset; |
|
/// |
|
/// if (offset < startOffset || offset > endOffset) { |
|
/// Close(); |
|
/// } else { |
|
/// codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset)); |
|
/// }]] |
|
/// </code> |
|
/// The Close method is called because the offset is out of the range. |
|
/// </remarks> |
|
void InsertCharacter(char ch) |
|
{ |
|
ActiveTextAreaControl.TextArea.MotherTextEditorControl.BeginUpdate(); |
|
|
|
switch (ActiveTextAreaControl.TextArea.Caret.CaretMode) |
|
{ |
|
case CaretMode.InsertMode: |
|
ActiveTextAreaControl.TextArea.InsertChar(ch); |
|
break; |
|
case CaretMode.OverwriteMode: |
|
ActiveTextAreaControl.TextArea.ReplaceChar(ch); |
|
break; |
|
} |
|
int currentLineNr = ActiveTextAreaControl.TextArea.Caret.Line; |
|
int delta = Document.FormattingStrategy.FormatLine(ActiveTextAreaControl.TextArea, currentLineNr, Document.PositionToOffset(ActiveTextAreaControl.TextArea.Caret.Position), ch); |
|
|
|
ActiveTextAreaControl.TextArea.MotherTextEditorControl.EndUpdate(); |
|
} |
|
|
|
/// <summary> |
|
/// Have to remove the bookmark from the document otherwise the text will |
|
/// stay marked in red if the bookmark is a breakpoint. This is because |
|
/// there are two bookmark managers, one in SharpDevelop itself and one |
|
/// in the TextEditor library. By default, only the one in the text editor's |
|
/// bookmark manager will be removed, so SharpDevelop will not know about it. |
|
/// Removing it from the SharpDevelop BookMarkManager informs the debugger |
|
/// service that the breakpoint has been removed so it triggers the removal |
|
/// of the text marker. |
|
/// </summary> |
|
void BookmarkRemoved(object sender, ICSharpCode.TextEditor.Document.BookmarkEventArgs e) |
|
{ |
|
ICSharpCode.SharpDevelop.Bookmarks.SDBookmark b = e.Bookmark as ICSharpCode.SharpDevelop.Bookmarks.SDBookmark; |
|
if (b != null) { |
|
ICSharpCode.SharpDevelop.Bookmarks.BookmarkManager.RemoveMark(b); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Have to add the bookmark to the BookmarkManager otherwise the bookmark is |
|
/// not remembered when re-opening the file and does not show in the |
|
/// bookmark manager. |
|
/// </summary> |
|
void BookmarkAdded(object sender, ICSharpCode.TextEditor.Document.BookmarkEventArgs e) |
|
{ |
|
ICSharpCode.SharpDevelop.Bookmarks.SDBookmark b = e.Bookmark as ICSharpCode.SharpDevelop.Bookmarks.SDBookmark; |
|
if (b != null) { |
|
ICSharpCode.SharpDevelop.Bookmarks.BookmarkManager.AddMark(b); |
|
} |
|
} |
|
} |
|
}
|
|
|