public void TestUndoCommand()
        {
            // Arrange
            var project = new Project();
            var context = new BlockCommandContext(project);
            ProjectBlockCollection blocks = project.Blocks;
            Block block = blocks[0];
            using (block.AcquireBlockLock(RequestLock.Write))
            {
                block.SetText("abcd");
            }
            int blockVersion = block.Version;
            BlockKey blockKey = block.BlockKey;

            var command = new ReplaceTextCommand(
                new BlockPosition(blockKey, 2), 1, "YES");
            project.Commands.Do(command, context);

            // Act
            project.Commands.Undo(context);

            // Assert
            Assert.AreEqual(1, blocks.Count);
            Assert.AreEqual(
                new BlockPosition(blocks[0], 3), project.Commands.LastPosition);

            const int index = 0;
            Assert.AreEqual("abcd", blocks[index].Text);
            Assert.AreEqual(blockVersion + 4, blocks[index].Version);
        }
        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);
        }
        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);
            }
        }
        /// <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;
        }