Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Krüger <mkrueger@xamarin.com>2016-07-26 14:48:46 +0300
committerMike Krüger <mkrueger@xamarin.com>2016-07-26 14:48:46 +0300
commitb3d8846fc868733febffff49cb8148df87398085 (patch)
tree8cdcf37cf126f4b5d3c0bf0f909c3cf374fbe80e /main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate
parent2008118fd5b012ac401b9dddfb156870e6a6f56c (diff)
[Ide] Implemented text mate language bundle indentation engine.
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate')
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/IIndentEngine.cs216
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateDocumentIndentEngine.cs196
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTextEditorExtension.cs72
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTracker.cs51
4 files changed, 535 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/IIndentEngine.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/IIndentEngine.cs
new file mode 100644
index 0000000000..0501cee653
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/IIndentEngine.cs
@@ -0,0 +1,216 @@
+//
+// IIndentEngine.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2016 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Collections.Generic;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.Editor.Highlighting.RegexEngine;
+
+namespace MonoDevelop.Ide.Editor.TextMate
+{
+ interface IDocumentIndentEngine
+ {
+ /// <summary>
+ /// The indentation string of the current line.
+ /// </summary>
+ string ThisLineIndent { get; }
+
+ /// <summary>
+ /// The indentation string of the next line.
+ /// </summary>
+ string NextLineIndent { get; }
+
+ /// <summary>
+ /// The current line number of the engine.
+ /// </summary>
+ int LineNumber { get; }
+
+ string CurrentIndent { get; }
+ new IDocumentIndentEngine Clone ();
+
+ /// <summary>
+ /// Resets the engine.
+ /// </summary>
+ void Reset();
+
+ void Push (IReadonlyTextDocument sourceText, IDocumentLine line);
+ }
+
+ /// <summary>
+ /// Represents a decorator of an IStateMachineIndentEngine instance that provides
+ /// logic for reseting and updating the engine on text changed events.
+ /// </summary>
+ /// <remarks>
+ /// The decorator is based on periodical caching of the engine's state and
+ /// delegating all logic behind indentation to the currently active engine.
+ /// </remarks>
+ class CacheIndentEngine : IDocumentIndentEngine
+ {
+
+ #region Properties
+
+ IDocumentIndentEngine currentEngine;
+ Stack<IDocumentIndentEngine> cachedEngines = new Stack<IDocumentIndentEngine> ();
+ readonly int cacheRate;
+
+ #endregion
+
+ #region Constructors
+
+ /// <summary>
+ /// Creates a new CacheIndentEngine instance.
+ /// </summary>
+ /// <param name="decoratedEngine">
+ /// An instance of <see cref="IDocumentIndentEngine"/> to which the
+ /// logic for indentation will be delegated.
+ /// </param>
+ /// <param name="cacheRate">
+ /// The number of lines between caching.
+ /// </param>
+ public CacheIndentEngine (IDocumentIndentEngine decoratedEngine, int cacheRate = 50)
+ {
+ this.cacheRate = cacheRate;
+ currentEngine = decoratedEngine;
+ }
+
+ /// <summary>
+ /// Creates a new CacheIndentEngine instance from the given prototype.
+ /// </summary>
+ /// <param name="prototype">
+ /// A CacheIndentEngine instance.
+ /// </param>
+ public CacheIndentEngine (CacheIndentEngine prototype)
+ {
+ currentEngine = prototype.currentEngine.Clone ();
+ }
+
+ #endregion
+
+ #region IDocumentIndentEngine
+
+ /// <inheritdoc />
+ public string ThisLineIndent {
+ get { return currentEngine.ThisLineIndent; }
+ }
+
+ /// <inheritdoc />
+ public string NextLineIndent {
+ get { return currentEngine.NextLineIndent; }
+ }
+
+ /// <inheritdoc />
+ public int LineNumber {
+ get { return currentEngine.LineNumber; }
+ }
+
+ /// <inheritdoc />
+ public string CurrentIndent {
+ get { return currentEngine.CurrentIndent; }
+ }
+
+ /// <inheritdoc />
+ public void Push (IReadonlyTextDocument sourceText, IDocumentLine line)
+ {
+ currentEngine.Push (sourceText, line);
+ }
+
+ /// <inheritdoc />
+ public void Reset ()
+ {
+ currentEngine.Reset ();
+ cachedEngines.Clear ();
+ }
+
+ /// <summary>
+ /// Resets the engine to offset. Clears all cached engines after the given offset.
+ /// </summary>
+ public void ResetEngineToPosition (int lineNumber)
+ {
+ // We are already there
+ if (currentEngine.LineNumber <= lineNumber)
+ return;
+
+ bool gotCachedEngine = false;
+ while (cachedEngines.Count > 0) {
+ var topEngine = cachedEngines.Peek ();
+ if (topEngine.LineNumber <= lineNumber) {
+ currentEngine = topEngine.Clone ();
+ gotCachedEngine = true;
+ break;
+ } else {
+ cachedEngines.Pop ();
+ }
+ }
+ if (!gotCachedEngine)
+ currentEngine.Reset ();
+ }
+
+ /// <inheritdoc />
+ public void Update (IReadonlyTextDocument sourceText, int lineNumber)
+ {
+ if (currentEngine.LineNumber == lineNumber) {
+ //positions match, nothing to be done
+ return;
+ } else if (currentEngine.LineNumber > lineNumber) {
+ //moving backwards, so reset from previous saved location
+ ResetEngineToPosition (lineNumber);
+ }
+
+ // get the engine caught up
+ int nextSave = (cachedEngines.Count == 0) ? cacheRate : cachedEngines.Peek ().LineNumber + cacheRate;
+ if (currentEngine.LineNumber + 1 == lineNumber) {
+ var line = sourceText.GetLine (currentEngine.LineNumber + 1);
+ currentEngine.Push (sourceText, line);
+ if (currentEngine.LineNumber == nextSave)
+ cachedEngines.Push (currentEngine.Clone ());
+ } else {
+ //bulk copy characters in case buffer is unmanaged
+ //(faster if we reduce managed/unmanaged transitions)
+ while (currentEngine.LineNumber < lineNumber) {
+ var line = sourceText.GetLine (currentEngine.LineNumber + 1);
+ if (line == null)
+ break;
+ int endCut = Math.Min (currentEngine.LineNumber + cacheRate, lineNumber);
+ for (int i = currentEngine.LineNumber; line != null && i < endCut; i++) {
+ currentEngine.Push (sourceText, line);
+ if (currentEngine.LineNumber == nextSave) {
+ cachedEngines.Push (currentEngine.Clone ());
+ nextSave += cacheRate;
+ }
+ line = line.NextLine;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ /// <inheritdoc />
+ public IDocumentIndentEngine Clone ()
+ {
+ return new CacheIndentEngine (this);
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateDocumentIndentEngine.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateDocumentIndentEngine.cs
new file mode 100644
index 0000000000..383b552cb3
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateDocumentIndentEngine.cs
@@ -0,0 +1,196 @@
+//
+// TextMateDocumentIndentEngine.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2016 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.Editor.Highlighting.RegexEngine;
+
+namespace MonoDevelop.Ide.Editor.TextMate
+{
+ class TextMateDocumentIndentEngine : IDocumentIndentEngine
+ {
+ readonly TextEditor editor;
+
+ bool increaseNextLine;
+ int indentLevel = 0;
+ int nextLineIndent = 0;
+ Regex increaseIndentPattern, decreaseIndentPattern, indentNextLinePattern, unIndentedLinePattern;
+
+ public int LineNumber {
+ get;
+ private set;
+ }
+
+ public string NextLineIndent {
+ get {
+ return CreateIndentString (nextLineIndent);
+ }
+ }
+
+ public string ThisLineIndent {
+ get {
+ return CreateIndentString (indentLevel);
+ }
+ }
+
+ string CreateIndentString (int indent)
+ {
+ return new string ('\t', indent / editor.Options.TabSize) + new string (' ', indent % editor.Options.TabSize);
+ }
+
+ public string CurrentIndent { get; private set; } = "";
+
+ public TextMateDocumentIndentEngine(TextEditor editor)
+ {
+ this.editor = editor;
+
+ var startScope = editor.SyntaxHighlighting.GetLinStartScopeStack (editor.GetLine (1));
+ foreach (var setting in SyntaxHighlightingService.GetSettings (startScope)) {
+ PObject val;
+ if (setting.TryGetSetting ("increaseIndentPattern", out val)) {
+ increaseIndentPattern = new Regex (((PString)val).Value);
+ }
+
+ if (setting.TryGetSetting ("indentNextLinePattern", out val)) {
+ indentNextLinePattern = new Regex (((PString)val).Value);
+ }
+
+ if (setting.TryGetSetting ("decreaseIndentPattern", out val)) {
+ decreaseIndentPattern = new Regex (((PString)val).Value);
+ }
+
+ if (setting.TryGetSetting ("unIndentedLinePattern", out val)) {
+ unIndentedLinePattern = new Regex (((PString)val).Value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// For unit testing.
+ /// </summary>
+ internal TextMateDocumentIndentEngine (TextEditor editor, Regex increaseIndentPattern, Regex decreaseIndentPattern, Regex indentNextLinePattern, Regex unIndentedLinePattern)
+ {
+ this.editor = editor;
+ this.increaseIndentPattern = increaseIndentPattern;
+ this.decreaseIndentPattern = decreaseIndentPattern;
+ this.indentNextLinePattern = indentNextLinePattern;
+ this.unIndentedLinePattern = unIndentedLinePattern;
+ }
+
+ public IDocumentIndentEngine Clone ()
+ {
+ return (IDocumentIndentEngine)MemberwiseClone ();
+ }
+
+ public void Push (IReadonlyTextDocument sourceText, IDocumentLine line)
+ {
+ if (sourceText == null)
+ throw new ArgumentNullException (nameof (sourceText));
+ if (line == null)
+ throw new ArgumentNullException (nameof (line));
+ int lineOffset = line.Offset;
+ LineNumber++;
+ CurrentIndent = line.GetIndentation (sourceText);
+
+ indentLevel = nextLineIndent;
+ if (CurrentIndent.Length == line.Length)
+ return;
+ nextLineIndent = GetIndentLevel (CurrentIndent);
+ if (increaseNextLine) {
+ DecreaseIndent (ref nextLineIndent);
+ increaseNextLine = false;
+ }
+ if (increaseIndentPattern != null) {
+ var match = increaseIndentPattern.Match (sourceText, lineOffset, line.LengthIncludingDelimiter);
+ if (match.Success) {
+ IncreaseIndent (ref nextLineIndent);
+ //var matchLen = lineOffset + line.LengthIncludingDelimiter - (match.Index + match.Length);
+ //if (matchLen <= 0)
+ // break;
+ //match = increaseIndentPattern.Match (sourceText, match.Index + match.Length, matchLen);
+ }
+ }
+
+ if (indentNextLinePattern != null) {
+ var match = indentNextLinePattern.Match (sourceText, lineOffset, line.LengthIncludingDelimiter);
+ if (match.Success) {
+ IncreaseIndent (ref nextLineIndent);
+ increaseNextLine = true;
+ }
+ }
+
+ if (decreaseIndentPattern != null) {
+ var match = decreaseIndentPattern.Match (sourceText, lineOffset, line.LengthIncludingDelimiter);
+ if (match.Success) {
+ DecreaseIndent (ref indentLevel);
+ DecreaseIndent (ref nextLineIndent);
+ //var matchLen = lineOffset + line.LengthIncludingDelimiter - (match.Index + match.Length);
+ //if (matchLen <= 0)
+ // break;
+ //match = decreaseIndentPattern.Match (sourceText, match.Index + match.Length, matchLen);
+ }
+ }
+ if (unIndentedLinePattern != null) {
+ var match = unIndentedLinePattern.Match (sourceText, lineOffset, line.LengthIncludingDelimiter);
+ if (match.Success)
+ indentLevel = 0;
+ }
+ }
+
+ int GetIndentLevel (string indent)
+ {
+ int result = 0;
+ foreach (var ch in indent) {
+ if (ch == '\t') {
+ IncreaseIndent (ref result);
+ } else {
+ result++;
+ }
+ }
+ return result;
+ }
+
+ void IncreaseIndent (ref int nextLineIndent)
+ {
+ nextLineIndent = (1 + nextLineIndent / editor.Options.TabSize) * editor.Options.TabSize;
+ }
+
+ void DecreaseIndent (ref int nextLineIndent)
+ {
+ nextLineIndent = Math.Max (0, (-1 + nextLineIndent / editor.Options.TabSize) * editor.Options.TabSize);
+ }
+
+ public void Reset ()
+ {
+ indentLevel = 0;
+ nextLineIndent = 0;
+ increaseNextLine = false;
+ LineNumber = 0;
+ CurrentIndent = "";
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTextEditorExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTextEditorExtension.cs
new file mode 100644
index 0000000000..f86afe0e08
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTextEditorExtension.cs
@@ -0,0 +1,72 @@
+//
+// TextMateIndentationTextEditorExtension.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2016 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using MonoDevelop.Ide.Editor.Extension;
+
+namespace MonoDevelop.Ide.Editor.TextMate
+{
+ class TextMateIndentationTextEditorExtension : TextEditorExtension
+ {
+ protected override void Initialize ()
+ {
+ Editor.SetIndentationTracker (new TextMateIndentationTracker (Editor));
+ }
+
+ public override bool KeyPress (KeyDescriptor descriptor)
+ {
+ if (descriptor.SpecialKey == SpecialKey.Return) {
+ if (Editor.Options.IndentStyle == IndentStyle.Virtual) {
+ if (Editor.GetLine (Editor.CaretLine).Length == 0)
+ Editor.CaretColumn = Editor.GetVirtualIndentationColumn (Editor.CaretLine);
+ } else {
+ DoReSmartIndent ();
+ var result = base.KeyPress (descriptor);
+ DoReSmartIndent ();
+ return result;
+ }
+ }
+ return base.KeyPress (descriptor);
+ }
+
+ void DoReSmartIndent ()
+ {
+ if (DefaultSourceEditorOptions.Instance.IndentStyle == IndentStyle.Auto) {
+ Editor.FixVirtualIndentation ();
+ return;
+ }
+ using (var undo = Editor.OpenUndoGroup ()) {
+ Editor.EnsureCaretIsNotVirtual ();
+ var indent = Editor.GetVirtualIndentationString (Editor.CaretLine);
+ var line = Editor.GetLine (Editor.CaretLine);
+ var actualIndent = line.GetIndentation (Editor);
+ if (actualIndent != indent) {
+ Editor.ReplaceText (line.Offset, actualIndent.Length, indent);
+ }
+ Editor.FixVirtualIndentation ();
+ }
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTracker.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTracker.cs
new file mode 100644
index 0000000000..142a08a4a9
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate/TextMateIndentationTracker.cs
@@ -0,0 +1,51 @@
+//
+// TextMateIndentationTracker.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2016 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using MonoDevelop.Ide.Editor.Extension;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.Editor.Highlighting.RegexEngine;
+
+namespace MonoDevelop.Ide.Editor.TextMate
+{
+ class TextMateIndentationTracker : IndentationTracker
+ {
+ readonly TextEditor editor;
+ readonly CacheIndentEngine engine;
+
+ public TextMateIndentationTracker (TextEditor editor)
+ {
+ this.editor = editor;
+ engine = new CacheIndentEngine (new TextMateDocumentIndentEngine (editor));
+ }
+
+ public override string GetIndentationString (int lineNumber)
+ {
+ engine.Update (editor, lineNumber);
+ return engine.ThisLineIndent;
+ }
+ }
+}