diff options
author | Mike Krüger <mkrueger@xamarin.com> | 2016-07-26 14:48:46 +0300 |
---|---|---|
committer | Mike Krüger <mkrueger@xamarin.com> | 2016-07-26 14:48:46 +0300 |
commit | b3d8846fc868733febffff49cb8148df87398085 (patch) | |
tree | 8cdcf37cf126f4b5d3c0bf0f909c3cf374fbe80e /main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate | |
parent | 2008118fd5b012ac401b9dddfb156870e6a6f56c (diff) |
[Ide] Implemented text mate language bundle indentation engine.
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.TextMate')
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; + } + } +} |