protected override void Undo(
			BlockCommandContext context,
			Block block)
        {
            // Revert the block type.
            block.SetBlockType(previousBlockType);
        }
        public static void GetCounts(
			IProjectPlugin project,
			Block block,
			out int count,
			out int wordCount,
			out int characterCount,
			out int nonWhitespaceCount)
        {
            // Make sure we have a sane state.
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }

            if (block == null)
            {
                throw new ArgumentNullException("block");
            }

            // Figure out the root path for the various components.
            HierarchicalPath rootPath = GetPluginRootPath(project);

            count = GetCount(block, rootPath, "Total/" + CountType);
            wordCount = GetCount(block, rootPath, "Total/" + WordCountType);
            characterCount = GetCount(block, rootPath, "Total/" + CharacterCountType);
            nonWhitespaceCount = GetCount(
                block, rootPath, "Total/" + NonWhitespaceCountType);
        }
        protected override void Do(
			BlockCommandContext context,
			Block block)
        {
            // Save the previous text so we can restore it.
            previousText = block.Text;
            originalPosition = context.Position;

            // Figure out what the new text string would be.
            startIndex = BlockPosition.TextIndex.GetCharacterIndex(
                block.Text, End, WordSearchDirection.Left);
            int endIndex = End.GetCharacterIndex(
                block.Text, TextIndex, WordSearchDirection.Right);

            int firstIndex = Math.Min(startIndex, endIndex);
            int lastIndex = Math.Max(startIndex, endIndex);
            string newText = block.Text.Remove(firstIndex, lastIndex - firstIndex);

            // Set the new text into the block. This will fire various events to
            // trigger the immediate and background processing.
            block.SetText(newText);

            // Set the position after the next text.
            if (UpdateTextPosition.HasFlag(DoTypes.Do))
            {
                context.Position = new BlockPosition(BlockKey, startIndex);
            }
        }
        /// <summary>
        /// Performs the command on the given block.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="block">The block to perform the action on.</param>
        /// <param name="project">The project that contains the current state.</param>
        protected override void Do(
			BlockCommandContext context,
			Block block)
        {
            // Save the previous text so we can undo it.
            previousText = block.Text;

            // Figure out what the new text string would be.
            int textIndex = new CharacterPosition(TextIndex).GetCharacterIndex(
                block.Text);
            string newText = block.Text.Insert(textIndex, Text);

            // Set the new text into the block. This will fire various events to
            // trigger the immediate and background processing.
            block.SetText(newText);

            // After we insert text, we need to give the immediate editor plugins a
            // chance to made any alterations to the output.
            block.Project.Plugins.ProcessImmediateEdits(
                context, block, textIndex + Text.Length);

            // Set the new position in the buffer.
            if (UpdateTextPosition.HasFlag(DoTypes.Do))
            {
                context.Position = new BlockPosition(BlockKey, textIndex + Text.Length);
            }
        }
        protected override void Do(
			BlockCommandContext context,
			Block block)
        {
            // Pull out some common elements we'll need.
            ProjectBlockCollection blocks = block.Blocks;
            int blockIndex = blocks.IndexOf(block) + 1;

            // Because of how block keys work, the ID is unique very time so we have
            // to update our inverse operation.
            addedBlocks.Clear();

            // Go through and create each block at a time, adding it to the inverse
            // command as we create them.
            for (int count = 0;
                count < Count;
                count++)
            {
                // Create and insert a new block into the system.
                var newBlock = new Block(blocks);
                blocks.Insert(blockIndex, newBlock);

                // Keep track of the block so we can remove them later.
                addedBlocks.Add(newBlock);

                // Update the position.
                if (UpdateTextPosition.HasFlag(DoTypes.Do))
                {
                    context.Position = new BlockPosition(newBlock.BlockKey, 0);
                }
            }
        }
        public InsertIndexedBlockCommand(
			int blockIndex,
			Block block)
        {
            BlockIndex = blockIndex;
            Block = block;
            UpdateTextPosition = DoTypes.All;
        }
        protected override void Undo(
			BlockCommandContext context,
			Block block)
        {
            block.SetText(previousText);
            if (UpdateTextPosition.HasFlag(DoTypes.Undo))
            {
                context.Position = new BlockPosition(BlockKey, previousText.Length);
            }
        }
        public BlockAnalyzer(
			Block block,
			int blockVersion,
			IList<IBlockAnalyzerProjectPlugin> blockAnalyzers,
			HashSet<IBlockAnalyzerProjectPlugin> analysis)
        {
            Block = block;
            BlockVersion = blockVersion;
            BlockAnalyzers = blockAnalyzers;
            Analysis = analysis;
        }
        protected override void Do(
			BlockCommandContext context,
			Block block)
        {
            previousText = block.Text;
            block.SetText(Text);

            if (UpdateTextPosition.HasFlag(DoTypes.Do))
            {
                context.Position = new BlockPosition(BlockKey, Text.Length);
            }
        }
        public void Do(BlockCommandContext context)
        {
            using (context.Blocks.AcquireLock(RequestLock.Write))
            {
                // We need the index of the block so we can restore it back into
                // its place.
                Block block = context.Blocks[blockKey];
                removedBlockIndex = context.Blocks.IndexOf(blockKey);
                removedBlock = block;

                // Delete the block from the list.
                context.Blocks.Remove(block);

                // If we have no more blocks, then we need to ensure we have a minimum
                // number of blocks.
                addedBlankBlock = null;

                if (!IgnoreMinimumLines
                    && context.Blocks.Count == 0)
                {
                    // Create a new placeholder block, which is blank.
                    addedBlankBlock = new Block(
                        context.Blocks, block.Project.BlockTypes.Paragraph);

                    context.Blocks.Add(addedBlankBlock);

                    if (UpdateTextPosition.HasFlag(DoTypes.Do))
                    {
                        context.Position = new BlockPosition(addedBlankBlock.BlockKey, 0);
                    }
                }
                else if (!IgnoreMinimumLines)
                {
                    // We have to figure out where the cursor would be after this operation.
                    // Ideally, this would be the block in the current position, but if this
                    // is the last line, then use that.
                    if (UpdateTextPosition.HasFlag(DoTypes.Do))
                    {
                        context.Position =
                            new BlockPosition(
                                removedBlockIndex < context.Blocks.Count
                                    ? context.Blocks[removedBlockIndex].BlockKey
                                    : context.Blocks[removedBlockIndex - 1].BlockKey,
                                0);
                    }
                }
            }
        }
        protected override void Do(
			BlockCommandContext context,
			Block block)
        {
            // We need to keep track of the previous block type so we can change
            // it back with Undo.
            previousBlockType = block.BlockType;

            // Set the block type.
            block.SetBlockType(BlockType);

            // Save the position from this command.
            if (UpdateTextPosition.HasFlag(DoTypes.Do))
            {
                context.Position = new BlockPosition(BlockKey, 0);
            }
        }
        public void ProcessImmediateEdits(
			BlockCommandContext context,
			Block block,
			int textIndex)
        {
            // Get the plugin settings from the project.
            ImmediateBlockTypesSettings settings = Settings;

            // Grab the substring from the beginning to the index and compare that
            // in the dictionary.
            string text = block.Text.Substring(0, textIndex);

            if (!settings.Replacements.ContainsKey(text))
            {
                // We want to fail as fast as possible.
                return;
            }

            // If the block type is already set to the same name, skip it.
            string blockTypeName = settings.Replacements[text];
            BlockType blockType = Project.BlockTypes[blockTypeName];

            if (block.BlockType == blockType)
            {
                return;
            }

            // Perform the substitution with a replace operation and a block change
            // operation.
            var replaceCommand =
                new ReplaceTextCommand(
                    new BlockPosition(block.BlockKey, 0), textIndex, string.Empty);
            var changeCommand = new ChangeBlockTypeCommand(block.BlockKey, blockType);

            // Create a composite command that binds everything together.
            var compositeCommand = new CompositeCommand<BlockCommandContext>(true, false);

            compositeCommand.Commands.Add(replaceCommand);
            compositeCommand.Commands.Add(changeCommand);

            // Add the command to the deferred execution so the command could
            // be properly handled via the undo/redo management.
            block.Project.Commands.DeferDo(compositeCommand);
        }
        /// <summary>
        /// Gets the deltas as a dictionary of key and deltas for the block.
        /// </summary>
        /// <param name="project">The project.</param>
        /// <param name="block">The block.</param>
        /// <param name="wordDelta">The word delta.</param>
        /// <param name="characterDelta">The character delta.</param>
        /// <param name="nonWhitespaceDelta">The non whitespace delta.</param>
        /// <returns>
        /// A dictionary of paths and deltas.
        /// </returns>
        public static Dictionary<HierarchicalPath, int> GetDeltas(
			IProjectPlugin project,
			Block block,
			int delta,
			int wordDelta,
			int characterDelta,
			int nonWhitespaceDelta)
        {
            // Make sure we have a sane arguments.
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }

            if (block == null)
            {
                throw new ArgumentNullException("block");
            }

            // Create the dictionary and figure out the top-level elements.
            var deltas = new Dictionary<HierarchicalPath, int>();
            HierarchicalPath rootPath = GetPluginRootPath(project);

            // Add in the path for the totals.
            var totalPath = new HierarchicalPath("Total", rootPath);

            AddDeltas(
                deltas, totalPath, delta, wordDelta, characterDelta, nonWhitespaceDelta);

            // Add in a block-type specific path along with a counter.
            string relativeBlockPath = "Block Types/" + block.BlockType.Name;
            var blockPath = new HierarchicalPath(relativeBlockPath, rootPath);

            AddDeltas(
                deltas, blockPath, delta, wordDelta, characterDelta, nonWhitespaceDelta);

            // Return the resulting delta.
            return deltas;
        }
        /// <summary>
        /// Raises an event that a block's text spans had changed.
        /// </summary>
        /// <param name="block"></param>
        public void RaiseTextSpansChanged(Block block)
        {
            EventHandler<BlockEventArgs> listeners = TextSpansChanged;

            if (listeners != null)
            {
                var args = new BlockEventArgs(block);
                listeners(this, args);
            }
        }
        /// <summary>
        /// Ensures the minimum blocks inside the collection.
        /// </summary>
        private void EnsureMinimumBlocks()
        {
            if (Count == 0)
            {
                var initialBlock = new Block(this);

                Add(initialBlock);
            }
        }
        public void ProcessImmediateEdits(
			BlockCommandContext context,
			Block block,
			int textIndex)
        {
            // If we aren't optimized, we have to pull the settings back in from the
            // project settings and optimize them.
            if (!optimizedSubstitions)
            {
                RetrieveSettings();
            }

            // Pull out the edit text and add a leading space to simplify the
            // "whole word" substitutions.
            string editText = block.Text.Substring(0, textIndex);

            if (editText.Length - 1 < 0)
            {
                return;
            }

            // Figure out if we're at a word break.
            char finalCharacter = editText[editText.Length - 1];
            bool isWordBreak = char.IsPunctuation(finalCharacter)
                || char.IsWhiteSpace(finalCharacter);

            // Go through the substitution elements and look for each one.
            foreach (RegisteredSubstitution substitution in Substitutions)
            {
                // If we are doing whole word searches, then we don't bother if
                // the final character isn't a word break or if it isn't a word
                // break before it.
                ReplaceTextCommand command;
                int searchLength = substitution.Search.Length;
                int startSearchIndex = editText.Length - searchLength;

                // If we are going to be searching before the string, then this
                // search term will never be valid.
                if (startSearchIndex < 0)
                {
                    continue;
                }

                // Do the search based on the whole word or suffix search.
                if (substitution.IsWholeWord)
                {
                    // Check to see if we have a valid search term.
                    if (!isWordBreak)
                    {
                        continue;
                    }

                    if (startSearchIndex > 0
                        && char.IsPunctuation(editText[startSearchIndex - 1]))
                    {
                        continue;
                    }

                    if (startSearchIndex - 1 < 0)
                    {
                        continue;
                    }

                    // Make sure the string we're looking at actually is the same.
                    string editSubstring = editText.Substring(
                        startSearchIndex - 1, substitution.Search.Length);

                    if (editSubstring != substitution.Search)
                    {
                        // The words don't match.
                        continue;
                    }

                    // Perform the substitution with a replace operation.
                    command =
                        new ReplaceTextCommand(
                            new BlockPosition(block.BlockKey, startSearchIndex - 1),
                            searchLength + 1,
                            substitution.Replacement + finalCharacter);
                }
                else
                {
                    // Perform a straight comparison search.
                    if (!editText.EndsWith(substitution.Search))
                    {
                        continue;
                    }

                    // Figure out the replace operation.
                    command =
                        new ReplaceTextCommand(
                            new BlockPosition(block.BlockKey, startSearchIndex),
                            searchLength,
                            substitution.Replacement);
                }

                // Add the command to the deferred execution so the command could
                // be properly handled via the undo/redo management.
                block.Project.Commands.DeferDo(command);
            }
        }
        public void AnalyzeBlock(
			Block block,
			int blockVersion)
        {
            // Grab the information about the block.
            string text;
            var originalMispelledWords = new TextSpanCollection();

            using (block.AcquireBlockLock(RequestLock.Read))
            {
                // If we are stale, then break out.
                if (block.IsStale(blockVersion))
                {
                    return;
                }

                // Grab the information from the block. We need the text and
                // alow the current spelling areas.
                text = block.Text;

                originalMispelledWords.AddRange(
                    block.TextSpans.Where(span => span.Controller == this));
            }

            // Split the word and perform spell-checking.
            var misspelledWords = new List<TextSpan>();
            IList<TextSpan> words = Splitter.SplitAndNormalize(text);
            IEnumerable<TextSpan> misspelledSpans =
                words.Where(span => !IsCorrect(span.GetText(text)));

            foreach (TextSpan span in misspelledSpans)
            {
                // We aren't correct, so add it to the list.
                span.Controller = this;

                misspelledWords.Add(span);
            }

            // Look to see if we have any change from the original spelling
            // errors and this one. This will only happen if the count is
            // identical and every one in the original list is in the new list.
            if (originalMispelledWords.Count == misspelledWords.Count)
            {
                bool isMatch = originalMispelledWords.All(misspelledWords.Contains);

                if (isMatch)
                {
                    // There are no new changes, so we don't have anything to
                    // update.
                    return;
                }
            }

            // Inside a write lock, we need to make modifications to the block's list.
            using (block.AcquireBlockLock(RequestLock.Write))
            {
                // Check one last time to see if the block is stale.
                if (block.IsStale(blockVersion))
                {
                    return;
                }

                // Make the changes to the block's contents.
                block.TextSpans.Remove(this);
                block.TextSpans.AddRange(misspelledWords);

                // Raise that we changed the spelling on the block.
                block.RaiseTextSpansChanged();
            }
        }
        /// <summary>
        /// Gets the editor actions associated with the given TextSpan.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="textSpan">The text span.</param>
        /// <returns>
        /// A list of editor actions associated with this span.
        /// </returns>
        /// <remarks>
        /// This will be called within a read-only lock.
        /// </remarks>
        public IList<IEditorAction> GetEditorActions(
			Block block,
			TextSpan textSpan)
        {
            // We only get to this point if we have a misspelled word.
            string word = textSpan.GetText(block.Text);

            // Get the suggestions for the word.
            IList<SpellingSuggestion> suggestions = GetSuggestions(word);

            // Go through the suggestions and create an editor action for each one.
            // These will already be ordered coming out of the GetSuggestions()
            // method.
            BlockCommandSupervisor commands = block.Project.Commands;
            var actions = new List<IEditorAction>(suggestions.Count);

            foreach (SpellingSuggestion suggestion in suggestions)
            {
                // Figure out the operation we'll be using to implement the change.
                var command =
                    new ReplaceTextCommand(
                        new BlockPosition(block.BlockKey, textSpan.StartTextIndex),
                        textSpan.Length,
                        suggestion.Suggestion);

                // Create the suggestion action, along with the replacement command.
                var action =
                    new EditorAction(
                        string.Format("Change to \"{0}\"", suggestion.Suggestion),
                        new HierarchicalPath("/Plugins/Spelling/Change"),
                        context => commands.Do(command, context));

                actions.Add(action);
            }

            // Add the additional editor actions from the plugins.
            foreach (ISpellingProjectPlugin controller in SpellingControllers)
            {
                IEnumerable<IEditorAction> additionalActions =
                    controller.GetAdditionalEditorActions(word);
                actions.AddRange(additionalActions);
            }

            // Return all the change actions.
            return actions;
        }
        /// <summary>
        /// Analyzes the block and counts the word. Once counted, this updates
        /// the block and all parent blocks with the altered change.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="blockVersion">The block version of the initial request.</param>
        public void AnalyzeBlock(
			Block block,
			int blockVersion)
        {
            // Grab counts from the current block text.
            int newCount = 1;
            int newWordCount;
            int newCharacterCount;
            int newNonWhitespaceCount;
            string text = block.Text;

            WordCounter.CountWords(
                text, out newWordCount, out newCharacterCount, out newNonWhitespaceCount);

            // Grab the existing counts from the current block, if we have one.
            int oldCount;
            int oldWordCount;
            int oldCharacterCount;
            int oldNonWhitespaceCount;

            WordCounterPathUtility.GetCounts(
                this,
                block,
                out oldCount,
                out oldWordCount,
                out oldCharacterCount,
                out oldNonWhitespaceCount);

            // Calculate the deltas between the values.
            int delta = newCount - oldCount;
            int wordDelta = newWordCount - oldWordCount;
            int characterDelta = newCharacterCount - oldCharacterCount;
            int nonWhitespaceDelta = newNonWhitespaceCount - oldNonWhitespaceCount;

            // Build up a dictionary of changes so we can have a simple loop to
            // set them in the various elements.
            Dictionary<HierarchicalPath, int> deltas =
                WordCounterPathUtility.GetDeltas(
                    this, block, delta, wordDelta, characterDelta, nonWhitespaceDelta);

            // Get a write lock on the blocks list and update that block and all
            // parent blocks in the document.
            using (block.AcquireBlockLock(RequestLock.Write))
            {
                // Log that we are analyzing this block.
                Log("BEGIN AnalyzeBlock: {0}: Words {1:N0}", block, newWordCount);

                // First check to see if we've gotten stale.
                if (block.IsStale(blockVersion))
                {
                    return;
                }

                // Update the block and the document.
                UpdateDeltas(block, deltas);
                UpdateDeltas(block.Project, deltas);

                // Log that we finished processing this block.
                Log("END   AnalyzeBlock: {0}: Words {1:N0}", block, newWordCount);
            }
        }
        /// <summary>
        /// Inserts the lines.
        /// </summary>
        /// <param name="project">The project.</param>
        /// <param name="lineCount">The line count.</param>
        private void InsertLines(
			Project project,
			int lineCount)
        {
            // Pull out some useful variables.
            ProjectBlockCollection blocks = project.Blocks;

            // Modify the first line, which is always there.
            using (blocks[0].AcquireBlockLock(RequestLock.Write))
            {
                blocks[0].SetText("Line 1");
            }

            // Add in the additional lines after the first one.
            for (int i = 1;
                i < lineCount;
                i++)
            {
                var block = new Block(blocks);
                using (block.AcquireBlockLock(RequestLock.Write))
                {
                    block.SetText("Line " + (i + 1));
                }
                blocks.Add(block);
            }
        }
		/// <summary>
		/// Gets the editor actions for a given text span.
		/// </summary>
		/// <param name="block">The block.</param>
		/// <param name="textSpan">The text span.</param>
		/// <returns></returns>
		public IList<IEditorAction> GetEditorActions(
			Block block,
			TextSpan textSpan)
		{
			// Loop through all the text span controllers and add their actions to
			// the list.
			IEnumerable<ProjectPluginController> controllers =
				Controllers.Where(
					controller => controller.ProjectPlugin is ITextControllerProjectPlugin);
			var actions = new List<IEditorAction>();

			foreach (ProjectPluginController controller in controllers)
			{
				var textSpanController =
					(ITextControllerProjectPlugin) controller.ProjectPlugin;
				IList<IEditorAction> controllerActions =
					textSpanController.GetEditorActions(block, textSpan);

				actions.AddRange(controllerActions);
			}

			// Return the resulting list of actions.
			return actions;
		}
        /// <summary>
        /// Acquires a lock on the collection, of the requested type, and also a lock
        /// on the block referenced by the index.
        /// </summary>
        /// <param name="blockIndex">The index of the block to lock.</param>
        /// <param name="block">The block retrieved by the index.</param>
        /// <param name="requestedBlockLock"></param>
        /// <returns>An opaque lock object that will release the lock on disposal.</returns>
        public IDisposable AcquireBlockLock(
			RequestLock requestedBlockLock,
			int blockIndex,
			out Block block)
        {
            return AcquireBlockLock(
                RequestLock.Read, requestedBlockLock, blockIndex, out block);
        }
        public void Do(BlockCommandContext context)
        {
            // We have to clear the undo buffer every time because we'll be creating
            // new blocks.
            addedBlocks.Clear();

            // Start by breaking apart the lines on the newline.
            string[] lines = Text.Split('\n');

            // Make changes to the first line by creating a command, adding it to the
            // list of commands we need an inverse for, and then performing it.
            Block block = context.Blocks[BlockPosition.BlockKey];
            string remainingText = block.Text.Substring((int) BlockPosition.TextIndex);
            deleteFirstCommand = new DeleteTextCommand(BlockPosition, block.Text.Length);
            insertFirstCommand = new InsertTextCommand(BlockPosition, lines[0]);

            deleteFirstCommand.Do(context);
            insertFirstCommand.Do(context);

            // Update the final lines text with the remains of the first line.
            int lastLineLength = lines[lines.Length - 1].Length;
            lines[lines.Length - 1] += remainingText;

            // For the remaining lines, we need to insert each one in turn.
            if (UpdateTextPosition.HasFlag(DoTypes.Do))
            {
                context.Position = BlockPosition.Empty;
            }

            if (lines.Length > 1)
            {
                // Go through all the lines in reverse order to insert them.
                int firstBlockIndex = context.Blocks.IndexOf(block);

                for (int i = lines.Length - 1;
                    i > 0;
                    i--)
                {
                    // Insert the line and set its text value.
                    var newBlock = new Block(context.Blocks);

                    addedBlocks.Add(newBlock);

                    using (newBlock.AcquireBlockLock(RequestLock.Write))
                    {
                        newBlock.SetText(lines[i]);
                    }

                    context.Blocks.Insert(firstBlockIndex + 1, newBlock);

                    // Update the last position as we go.
                    if (context.Position == BlockPosition.Empty)
                    {
                        if (UpdateTextPosition.HasFlag(DoTypes.Do))
                        {
                            context.Position = new BlockPosition(
                                newBlock.BlockKey, (CharacterPosition) lastLineLength);
                        }
                    }
                }
            }
        }
        public void ChangeBlockType(
			Block block,
			BlockType oldBlockType)
        {
            // We need a write lock on the blocks while we make this change.
            using (block.AcquireBlockLock(RequestLock.Write))
            {
                // Report what we're doing if we have logging on.
                Log("ChangeBlockType: {0}: Old Type {1}", block, oldBlockType);

                //// Figure out the deltas for this block.
                //var deltas = new Dictionary<HierarchicalPath, int>();
                //deltas[WordCounterPathUtility.GetPath(oldBlockType)] = -1;
                //deltas[WordCounterPathUtility.GetPath(block.BlockType)] = 1;

                //// Update the parent types.
                //UpdateDeltas(block, deltas);
                //UpdateDeltas(block.Project, deltas);
            }
        }
        public void Undo(BlockCommandContext context)
        {
            using (context.Blocks.AcquireLock(RequestLock.Write))
            {
                // Insert in the old block.
                context.Blocks.Insert(removedBlockIndex, removedBlock);

                // Set the last text position.
                if (UpdateTextPosition.HasFlag(DoTypes.Undo))
                {
                    context.Position = new BlockPosition(blockKey, removedBlock.Text.Length);
                }

                // Remove the blank block, if we added one.
                if (addedBlankBlock != null)
                {
                    context.Blocks.Remove(addedBlankBlock);
                    addedBlankBlock = null;
                }
            }
        }
        protected override void Undo(
			BlockCommandContext context,
			Block block)
        {
            foreach (Block addedBlock in addedBlocks)
            {
                context.Blocks.Remove(addedBlock);
            }

            if (UpdateTextPosition.HasFlag(DoTypes.Undo))
            {
                context.Position = new BlockPosition(BlockKey, block.Text.Length);
            }
        }
		/// <summary>
		/// Processes any block analysis on the given block.
		/// </summary>
		/// <param name="block">The block.</param>
		public async void ProcessBlockAnalysis(Block block)
		{
			// If we don't have any analysis controllers, we don't have to do anything.
			if (BlockAnalyzers.Count == 0)
			{
				return;
			}

			// Keep track of the running tasks so we can wait for them to finish
			// (if needed).
			Interlocked.Increment(ref tasksRunning);

			try
			{
				// Grab information about the block inside a read lock.
				int blockVersion;
				HashSet<IBlockAnalyzerProjectPlugin> analysis;

				using (block.AcquireBlockLock(RequestLock.Read))
				{
					blockVersion = block.Version;
					analysis = block.GetAnalysis();
				}

				// Create a background task that will analyze the block. This will return
				// false if the block had changed in the process of analysis (which would
				// have triggered another background task).
				var analyzer = new BlockAnalyzer(
					block, blockVersion, BlockAnalyzers, analysis);
				Task task = Task.Factory.StartNew(analyzer.Run);

				// Wait for the task to complete in the background so we can then
				// decrement our running counter.
				await task;
			}
			finally
			{
				// Decrement the counter to keep track of running tasks.
				Interlocked.Decrement(ref tasksRunning);
			}
		}
		/// <summary>
		/// Checks for immediate edits on a block. This is intended to be a blocking
		/// editing that will always happen within a write lock.
		/// </summary>
		/// <param name="context">The context of the edit just performed.</param>
		/// <param name="block">The block associated with the current changes.</param>
		/// <param name="textIndex">Index of the text inside the block.</param>
		public void ProcessImmediateEdits(
			BlockCommandContext context,
			Block block,
			int textIndex)
		{
			foreach (
				IImmediateEditorProjectPlugin immediateBlockEditor in ImmediateEditors)
			{
				immediateBlockEditor.ProcessImmediateEdits(context, block, textIndex);
			}
		}
        public override LineBufferOperationResults InsertLines(
			int lineIndex,
			int count)
        {
            var composite = new CompositeCommand<BlockCommandContext>(true, false);

            using (project.Blocks.AcquireLock(RequestLock.Write))
            {
                for (int i = 0;
                    i < count;
                    i++)
                {
                    var block = new Block(project.Blocks);
                    var command = new InsertIndexedBlockCommand(lineIndex, block);
                    composite.Commands.Add(command);
                }

                var context = new BlockCommandContext(project);
                project.Commands.Do(composite, context);

                return GetOperationResults();
            }
        }
        protected abstract void Undo(
			BlockCommandContext context,
			Block block);