// // CacheIndentEngine.cs // // Author: // Matej Miklečić // // Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) // // 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 ICSharpCode.NRefactory.Editor; using System; using System.Collections.Generic; using System.Linq; namespace ICSharpCode.NRefactory.CSharp { /// /// Represents a decorator of an IStateMachineIndentEngine instance that provides /// logic for reseting and updating the engine on text changed events. /// /// /// The decorator is based on periodical caching of the engine's state and /// delegating all logic behind indentation to the currently active engine. /// public class CacheIndentEngine : IStateMachineIndentEngine { #region Properties IStateMachineIndentEngine currentEngine; Stack cachedEngines = new Stack(); #endregion #region Constructors /// /// Creates a new CacheIndentEngine instance. /// /// /// An instance of to which the /// logic for indentation will be delegated. /// /// /// The number of chars between caching. /// public CacheIndentEngine(IStateMachineIndentEngine decoratedEngine, int cacheRate = 2000) { this.currentEngine = decoratedEngine; } /// /// Creates a new CacheIndentEngine instance from the given prototype. /// /// /// A CacheIndentEngine instance. /// public CacheIndentEngine(CacheIndentEngine prototype) { this.currentEngine = prototype.currentEngine.Clone(); } #endregion #region IDocumentIndentEngine /// public IDocument Document { get { return currentEngine.Document; } } /// public string ThisLineIndent { get { return currentEngine.ThisLineIndent; } } /// public string NextLineIndent { get { return currentEngine.NextLineIndent; } } /// public string CurrentIndent { get { return currentEngine.CurrentIndent; } } /// public bool NeedsReindent { get { return currentEngine.NeedsReindent; } } /// public int Offset { get { return currentEngine.Offset; } } /// public TextLocation Location { get { return currentEngine.Location; } } /// public bool EnableCustomIndentLevels { get { return currentEngine.EnableCustomIndentLevels; } set { currentEngine.EnableCustomIndentLevels = value; } } /// public void Push(char ch) { currentEngine.Push(ch); } /// public void Reset() { currentEngine.Reset(); cachedEngines.Clear(); } /// /// Resets the engine to offset. Clears all cached engines after the given offset. /// public void ResetEngineToPosition(int offset) { // We are already there if (currentEngine.Offset <= offset) return; bool gotCachedEngine = false; while (cachedEngines.Count > 0) { var topEngine = cachedEngines.Peek(); if (topEngine.Offset <= offset) { currentEngine = topEngine.Clone(); gotCachedEngine = true; break; } else { cachedEngines.Pop(); } } if (!gotCachedEngine) currentEngine.Reset(); } /// /// /// If the is negative, the engine will /// update to: document.TextLength + (offset % document.TextLength+1) /// Otherwise it will update to: offset % document.TextLength+1 /// public void Update(int position) { const int BUFFER_SIZE = 2000; if (currentEngine.Offset == position) { //positions match, nothing to be done return; } else if (currentEngine.Offset > position) { //moving backwards, so reset from previous saved location ResetEngineToPosition(position); } // get the engine caught up int nextSave = (cachedEngines.Count == 0) ? BUFFER_SIZE : cachedEngines.Peek().Offset + BUFFER_SIZE; if (currentEngine.Offset + 1 == position) { char ch = currentEngine.Document.GetCharAt(currentEngine.Offset); currentEngine.Push(ch); if (currentEngine.Offset == nextSave) cachedEngines.Push(currentEngine.Clone()); } else { //bulk copy characters in case buffer is unmanaged //(faster if we reduce managed/unmanaged transitions) while (currentEngine.Offset < position) { int endCut = currentEngine.Offset + BUFFER_SIZE; if (endCut > position) endCut = position; string buffer = currentEngine.Document.GetText(currentEngine.Offset, endCut - currentEngine.Offset); foreach (char ch in buffer) { currentEngine.Push(ch); //ConsoleWrite ("pushing character '{0}'", ch); if (currentEngine.Offset == nextSave) { cachedEngines.Push(currentEngine.Clone()); nextSave += BUFFER_SIZE; } } } } } public IStateMachineIndentEngine GetEngine(int offset) { ResetEngineToPosition(offset); return currentEngine; } #endregion #region IClonable /// public IStateMachineIndentEngine Clone() { return new CacheIndentEngine(this); } /// IDocumentIndentEngine IDocumentIndentEngine.Clone() { return Clone(); } object ICloneable.Clone() { return Clone(); } #endregion #region IStateMachineIndentEngine public bool IsInsidePreprocessorDirective { get { return currentEngine.IsInsidePreprocessorDirective; } } public bool IsInsidePreprocessorComment { get { return currentEngine.IsInsidePreprocessorComment; } } public bool IsInsideStringLiteral { get { return currentEngine.IsInsideStringLiteral; } } public bool IsInsideVerbatimString { get { return currentEngine.IsInsideVerbatimString; } } public bool IsInsideCharacter { get { return currentEngine.IsInsideCharacter; } } public bool IsInsideString { get { return currentEngine.IsInsideString; } } public bool IsInsideLineComment { get { return currentEngine.IsInsideLineComment; } } public bool IsInsideMultiLineComment { get { return currentEngine.IsInsideMultiLineComment; } } public bool IsInsideDocLineComment { get { return currentEngine.IsInsideDocLineComment; } } public bool IsInsideComment { get { return currentEngine.IsInsideComment; } } public bool IsInsideOrdinaryComment { get { return currentEngine.IsInsideOrdinaryComment; } } public bool IsInsideOrdinaryCommentOrString { get { return currentEngine.IsInsideOrdinaryCommentOrString; } } public bool LineBeganInsideVerbatimString { get { return currentEngine.LineBeganInsideVerbatimString; } } public bool LineBeganInsideMultiLineComment { get { return currentEngine.LineBeganInsideMultiLineComment; } } #endregion } /* / // /// Represents a decorator of an IStateMachineIndentEngine instance that provides /// logic for reseting and updating the engine on text changed events. /// /// /// The decorator is based on periodical caching of the engine's state and /// delegating all logic behind indentation to the currently active engine. /// public class CacheIndentEngine : IStateMachineIndentEngine { #region Properties /// /// Represents the cache interval in number of chars pushed to the engine. /// /// /// When this many new chars are pushed to the engine, the currently active /// engine gets cloned and added to the end of . /// readonly int cacheRate; /// /// Determines how much memory to reserve on initialization for the /// cached engines. /// const int cacheCapacity = 25; /// /// Currently active engine. /// /// /// Should be equal to the last engine in . /// IStateMachineIndentEngine currentEngine; /// /// List of cached engines sorted ascending by /// . /// IStateMachineIndentEngine[] cachedEngines; /// /// The index of the last cached engine in cachedEngines. /// /// /// Should be equal to: currentEngine.Offset / CacheRate /// int lastCachedEngine; #endregion #region Constructors /// /// Creates a new CacheIndentEngine instance. /// /// /// An instance of to which the /// logic for indentation will be delegated. /// /// /// The number of chars between caching. /// public CacheIndentEngine(IStateMachineIndentEngine decoratedEngine, int cacheRate = 2000) { this.cachedEngines = new IStateMachineIndentEngine[cacheCapacity]; this.cachedEngines[0] = decoratedEngine.Clone(); this.currentEngine = this.cachedEngines[0].Clone(); this.cacheRate = cacheRate; } /// /// Creates a new CacheIndentEngine instance from the given prototype. /// /// /// A CacheIndentEngine instance. /// public CacheIndentEngine(CacheIndentEngine prototype) { this.cachedEngines = new IStateMachineIndentEngine[prototype.cachedEngines.Length]; Array.Copy(prototype.cachedEngines, this.cachedEngines, prototype.cachedEngines.Length); this.lastCachedEngine = prototype.lastCachedEngine; this.currentEngine = prototype.currentEngine.Clone(); this.cacheRate = prototype.cacheRate; } #endregion #region Methods /// /// Performs caching of the . /// void cache() { if (currentEngine.Offset % cacheRate != 0) { throw new Exception("The current engine's offset is not divisable with the cacheRate."); } // determine the new current engine from cachedEngines lastCachedEngine = currentEngine.Offset / cacheRate; if (cachedEngines.Length < lastCachedEngine + 1) { Array.Resize(ref cachedEngines, lastCachedEngine * 2); } cachedEngines[lastCachedEngine] = currentEngine.Clone(); } #endregion #region IDocumentIndentEngine /// public IDocument Document { get { return currentEngine.Document; } } /// public string ThisLineIndent { get { return currentEngine.ThisLineIndent; } } /// public string NextLineIndent { get { return currentEngine.NextLineIndent; } } /// public string CurrentIndent { get { return currentEngine.CurrentIndent; } } /// public bool NeedsReindent { get { return currentEngine.NeedsReindent; } } /// public int Offset { get { return currentEngine.Offset; } } /// public TextLocation Location { get { return currentEngine.Location; } } /// public void Push(char ch) { currentEngine.Push(ch); if (currentEngine.Offset % cacheRate == 0) { cache(); } } /// public void Reset() { currentEngine = cachedEngines[lastCachedEngine = 0]; } /// /// /// If the is negative, the engine will /// update to: document.TextLength + (offset % document.TextLength+1) /// Otherwise it will update to: offset % document.TextLength+1 /// public void Update(int offset) { // map the given offset to the [0, document.TextLength] interval // using modulo arithmetics offset %= Document.TextLength + 1; if (offset < 0) { offset += Document.TextLength + 1; } // check if the engine has to be updated to some previous offset if (currentEngine.Offset > offset) { // replace the currentEngine with the first one whose offset // is less then the given lastCachedEngine = offset / cacheRate; currentEngine = cachedEngines[lastCachedEngine].Clone(); } // update the engine to the given offset while (Offset < offset) { Push(Document.GetCharAt(Offset)); } } public IStateMachineIndentEngine GetEngine(int offset) { // map the given offset to the [0, document.TextLength] interval // using modulo arithmetics offset %= Document.TextLength + 1; if (offset < 0) { offset += Document.TextLength + 1; } // check if the engine has to be updated to some previous offset if (currentEngine.Offset > offset) { // replace the currentEngine with the first one whose offset // is less then the given lastCachedEngine = offset / cacheRate; return cachedEngines[lastCachedEngine].Clone(); } return currentEngine; } #endregion #region IClonable /// public IStateMachineIndentEngine Clone() { return new CacheIndentEngine(this); } /// IDocumentIndentEngine IDocumentIndentEngine.Clone() { return Clone(); } object ICloneable.Clone() { return Clone(); } #endregion #region IStateMachineIndentEngine public bool IsInsidePreprocessorDirective { get { return currentEngine.IsInsidePreprocessorDirective; } } public bool IsInsidePreprocessorComment { get { return currentEngine.IsInsidePreprocessorComment; } } public bool IsInsideStringLiteral { get { return currentEngine.IsInsideStringLiteral; } } public bool IsInsideVerbatimString { get { return currentEngine.IsInsideVerbatimString; } } public bool IsInsideCharacter { get { return currentEngine.IsInsideCharacter; } } public bool IsInsideString { get { return currentEngine.IsInsideString; } } public bool IsInsideLineComment { get { return currentEngine.IsInsideLineComment; } } public bool IsInsideMultiLineComment { get { return currentEngine.IsInsideMultiLineComment; } } public bool IsInsideDocLineComment { get { return currentEngine.IsInsideDocLineComment; } } public bool IsInsideComment { get { return currentEngine.IsInsideComment; } } public bool IsInsideOrdinaryComment { get { return currentEngine.IsInsideOrdinaryComment; } } public bool IsInsideOrdinaryCommentOrString { get { return currentEngine.IsInsideOrdinaryCommentOrString; } } public bool LineBeganInsideVerbatimString { get { return currentEngine.LineBeganInsideVerbatimString; } } public bool LineBeganInsideMultiLineComment { get { return currentEngine.LineBeganInsideMultiLineComment; } } #endregion } */ }