演练:将 shell 命令与编辑器扩展配合使用

在 VSPackage 中,可以将菜单命令等功能添加到编辑器中。 本演练演示如何通过调用菜单命令向编辑器中的文本视图添加装饰。

本演练演示如何将 VSPackage 与托管扩展性框架(MEF)组件部件结合使用。 必须使用 VSPackage 向 Visual Studio shell 注册菜单命令。 还可以使用命令访问 MEF 组件部件。

使用菜单命令创建扩展

创建一个 VSPackage,用于在“工具”菜单上放置名为“添加装饰”菜单命令。

  1. 创建名为 MenuCommandTestC# VSIX 的项目,并添加自定义命令项模板名称 AddAdornment。 有关详细信息,请参阅 使用菜单命令创建扩展。

  2. 此时会打开名为 MenuCommandTest 的解决方案。 MenuCommandTestPackage 文件包含创建菜单命令的代码,并将其 放在“工具” 菜单上。 此时,该命令只会显示一个消息框。 后面的步骤将演示如何更改此项以显示注释装饰。

  3. VSIX 清单编辑器中打开 source.extension.vsixmanifest 文件。 选项卡 Assets 应为名为 MenuCommandTest 的 Microsoft.VisualStudio.VsPackage 创建一行。

  4. 保存并关闭 source.extension.vsixmanifest 文件。

将 MEF 扩展添加到命令扩展

  1. 解决方案资源管理器中,右键单击解决方案节点,单击“添加,然后单击“新建项目”。 在“添加新项目”对话框中,单击 Visual C# 下的“扩展性,然后单击“VSIX 项目”。 将项目命名为 CommentAdornmentTest

  2. 由于此项目将与强命名的 VSPackage 程序集交互,因此必须对程序集进行签名。 可以重复使用为 VSPackage 程序集创建的密钥文件。

    1. 打开项目属性并选择“ 签名 ”选项卡。

    2. 选择“ 对程序集进行签名”。

    3. “选择强名称密钥文件”下,选择 为 MenuCommandTest 程序集生成的 Key.snk 文件。

请参阅 VSPackage 项目中的 MEF 扩展

由于要向 VSPackage 添加 MEF 组件,因此必须在清单中指定这两种类型的资产。

注意

有关 MEF 的详细信息,请参阅 Managed Extensibility Framework (MEF)

引用 VSPackage 项目中的 MEF 组件

  1. 在 MenuCommandTest 项目中,在 VSIX 清单编辑器中打开 source.extension.vsixmanifest 文件。

  2. “资产 ”选项卡上,单击“ 新建”。

  3. 在“类型”列表中,选择“Microsoft.VisualStudio.MefComponent” 。

  4. 在“源”列表中,选择“当前解决方案中的项目”。

  5. “项目” 列表中,选择 “CommentAdornmentTest”。

  6. 保存并关闭 source.extension.vsixmanifest 文件。

  7. 确保 MenuCommandTest 项目具有对 CommentAdornmentTest 项目的引用。

  8. 在 CommentAdornmentTest 项目中,将项目设置为生成程序集。 在解决方案资源管理器中,选择项目并在“属性”窗口中查找“将生成输出复制到 OutputDirectory 属性”,并将其设置为 true

定义注释装饰

注释装饰本身包含一个 ITrackingSpan 跟踪所选文本的字符串,以及一些表示作者和文本说明的字符串。

定义注释装饰

  1. 在 CommentAdornmentTest 项目中,添加新的类文件并将其命名 CommentAdornment

  2. 添加以下引用:

    1. Microsoft.VisualStudio.CoreUtility

    2. Microsoft.VisualStudio.Text.Data

    3. Microsoft.VisualStudio.Text.Logic

    4. Microsoft.VisualStudio.Text.UI

    5. Microsoft.VisualStudio.Text.UI.Wpf

    6. System.ComponentModel.Composition

    7. PresentationCore

    8. PresentationFramework

    9. WindowsBase

  3. 添加以下 using 指令。

    using Microsoft.VisualStudio.Text;
    
  4. 该文件应包含一个名为 CommentAdornment..

    internal class CommentAdornment
    
  5. CommentAdornment 类中添加三个字段,供 ITrackingSpan作者和说明使用。

    public readonly ITrackingSpan Span;
    public readonly string Author;
    public readonly string Text;
    
  6. 添加初始化字段的构造函数。

    public CommentAdornment(SnapshotSpan span, string author, string text)
    {
        this.Span = span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeExclusive);
        this.Author = author;
        this.Text = text;
    }
    

为装饰创建视觉元素

为装饰定义视觉元素。 在本演练中,定义继承自 Windows Presentation Foundation (WPF) 类 Canvas的控件。

  1. 在 CommentAdornmentTest 项目中创建一个类,并将其命名 CommentBlock

  2. 添加以下 using 指令。

    using Microsoft.VisualStudio.Text;
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Utilities;
    
  3. CommentBlock使类继承自 Canvas.

    internal class CommentBlock : Canvas
    { }
    
  4. 添加一些专用字段以定义装饰的视觉方面。

    private Geometry textGeometry;
    private Grid commentGrid;
    private static Brush brush;
    private static Pen solidPen;
    private static Pen dashPen;
    
  5. 添加一个构造函数,该构造函数定义注释装饰并添加相关文本。

    public CommentBlock(double textRightEdge, double viewRightEdge,
            Geometry newTextGeometry, string author, string body)
    {
        if (brush == null)
        {
            brush = new SolidColorBrush(Color.FromArgb(0x20, 0x00, 0xff, 0x00));
            brush.Freeze();
            Brush penBrush = new SolidColorBrush(Colors.Green);
            penBrush.Freeze();
            solidPen = new Pen(penBrush, 0.5);
            solidPen.Freeze();
            dashPen = new Pen(penBrush, 0.5);
            dashPen.DashStyle = DashStyles.Dash;
            dashPen.Freeze();
        }
    
        this.textGeometry = newTextGeometry;
    
        TextBlock tb1 = new TextBlock();
        tb1.Text = author;
        TextBlock tb2 = new TextBlock();
        tb2.Text = body;
    
        const int MarginWidth = 8;
        this.commentGrid = new Grid();
        this.commentGrid.RowDefinitions.Add(new RowDefinition());
        this.commentGrid.RowDefinitions.Add(new RowDefinition());
        ColumnDefinition cEdge = new ColumnDefinition();
        cEdge.Width = new GridLength(MarginWidth);
        ColumnDefinition cEdge2 = new ColumnDefinition();
        cEdge2.Width = new GridLength(MarginWidth);
        this.commentGrid.ColumnDefinitions.Add(cEdge);
        this.commentGrid.ColumnDefinitions.Add(new ColumnDefinition());
        this.commentGrid.ColumnDefinitions.Add(cEdge2);
    
        System.Windows.Shapes.Rectangle rect = new System.Windows.Shapes.Rectangle();
        rect.RadiusX = 6;
        rect.RadiusY = 3;
        rect.Fill = brush;
        rect.Stroke = Brushes.Green;
    
            Size inf = new Size(double.PositiveInfinity, double.PositiveInfinity);
            tb1.Measure(inf);
            tb2.Measure(inf);
            double middleWidth = Math.Max(tb1.DesiredSize.Width, tb2.DesiredSize.Width);
            this.commentGrid.Width = middleWidth + 2 * MarginWidth;
    
        Grid.SetColumn(rect, 0);
        Grid.SetRow(rect, 0);
        Grid.SetRowSpan(rect, 2);
        Grid.SetColumnSpan(rect, 3);
        Grid.SetRow(tb1, 0);
        Grid.SetColumn(tb1, 1);
        Grid.SetRow(tb2, 1);
        Grid.SetColumn(tb2, 1);
        this.commentGrid.Children.Add(rect);
        this.commentGrid.Children.Add(tb1);
        this.commentGrid.Children.Add(tb2);
    
        Canvas.SetLeft(this.commentGrid, Math.Max(viewRightEdge - this.commentGrid.Width - 20.0, textRightEdge + 20.0));
        Canvas.SetTop(this.commentGrid, textGeometry.GetRenderBounds(solidPen).Top);
    
        this.Children.Add(this.commentGrid);
    }
    
  6. 同时实现绘制 OnRender 装饰的事件处理程序。

    protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);
        if (this.textGeometry != null)
        {
            dc.DrawGeometry(brush, solidPen, this.textGeometry);
            Rect textBounds = this.textGeometry.GetRenderBounds(solidPen);
            Point p1 = new Point(textBounds.Right, textBounds.Bottom);
            Point p2 = new Point(Math.Max(Canvas.GetLeft(this.commentGrid) - 20.0, p1.X), p1.Y);
            Point p3 = new Point(Math.Max(Canvas.GetLeft(this.commentGrid), p1.X), (Canvas.GetTop(this.commentGrid) + p1.Y) * 0.5);
            dc.DrawLine(dashPen, p1, p2);
            dc.DrawLine(dashPen, p2, p3);
        }
    }
    

添加 IWpfTextViewCreationListener

IWpfTextViewCreationListener这是一个 MEF 组件部件,可用于侦听创建事件。

  1. 将类文件添加到 CommentAdornmentTest 项目并将其命名 Connector

  2. 添加以下 using 指令。

    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Utilities;
    
  3. 声明实现IWpfTextViewCreationListener的类,并使用“text”和 ContentTypeAttribute/> 的DocumentTextViewRoleAttribute导出。 内容类型属性指定组件应用到的内容类型。 文本类型是所有非二进制文件类型的基类型。 因此,创建的几乎所有文本视图都将是此类型。 文本视图角色属性指定组件应用到的文本视图的类型。 文档文本视图角色通常显示由行组成且存储在文件中的文本。

    [Export(typeof(IWpfTextViewCreationListener))]
    [ContentType("text")]
    [TextViewRole(PredefinedTextViewRoles.Document)]
    public sealed class Connector : IWpfTextViewCreationListener
    
  4. 实现该方法TextViewCreated,以便调用该CommentAdornmentManager方法的静态Create()事件。

    public void TextViewCreated(IWpfTextView textView)
    {
        CommentAdornmentManager.Create(textView);
    }
    
  5. 添加可用于执行命令的方法。

    static public void Execute(IWpfTextViewHost host)
    {
        IWpfTextView view = host.TextView;
        //Add a comment on the selected text. 
        if (!view.Selection.IsEmpty)
        {
            //Get the provider for the comment adornments in the property bag of the view.
            CommentAdornmentProvider provider = view.Properties.GetProperty<CommentAdornmentProvider>(typeof(CommentAdornmentProvider));
    
            //Add some arbitrary author and comment text. 
            string author = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
            string comment = "Four score....";
    
            //Add the comment adornment using the provider.
            provider.Add(view.Selection.SelectedSpans[0], author, comment);
        }
    }
    

定义装饰层

若要添加新装饰,必须定义装饰层。

定义装饰层

  1. 在类中 Connector ,声明一个类型 AdornmentLayerDefinition的公共字段,并使用指定装饰层的唯一名称导出它 NameAttribute ,以及 OrderAttribute 定义此装饰层与其他文本视图层(文本、插入点和选定内容)的 Z 顺序关系。

    [Export(typeof(AdornmentLayerDefinition))]
    [Name("CommentAdornmentLayer")]
    [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
    public AdornmentLayerDefinition commentLayerDefinition;
    
    

提供注释装饰

定义装饰时,还实现注释装饰提供程序和注释装饰管理器。 注释修饰提供程序保留注释装饰列表,侦 Changed 听基础文本缓冲区上的事件,并在删除基础文本时删除注释装饰。

  1. 向 CommentAdornmentTest 项目添加新的类文件并将其命名 CommentAdornmentProvider

  2. 添加以下 using 指令。

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    
  3. 添加名为的 CommentAdornmentProvider 的类。

    internal class CommentAdornmentProvider
    {
    }
    
  4. 为文本缓冲区和与缓冲区相关的注释装饰列表添加专用字段。

    private ITextBuffer buffer;
    private IList<CommentAdornment> comments = new List<CommentAdornment>();
    
    
  5. 为 .添加构造函数CommentAdornmentProvider 此构造函数应具有专用访问权限,因为提供程序由 Create() 该方法实例化。 构造函数将 OnBufferChanged 事件处理程序添加到事件 Changed

    private CommentAdornmentProvider(ITextBuffer buffer)
    {
        this.buffer = buffer;
        //listen to the Changed event so we can react to deletions. 
        this.buffer.Changed += OnBufferChanged;
    }
    
    
  6. 添加 Create() 方法。

    public static CommentAdornmentProvider Create(IWpfTextView view)
    {
        return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentProvider>(delegate { return new CommentAdornmentProvider(view.TextBuffer); });
    }
    
    
  7. 添加 Detach() 方法。

    public void Detach()
    {
        if (this.buffer != null)
        {
            //remove the Changed listener 
            this.buffer.Changed -= OnBufferChanged;
            this.buffer = null;
        }
    }
    
  8. OnBufferChanged添加事件处理程序。

    private void OnBufferChanged(object sender, TextContentChangedEventArgs e)
    {
        //Make a list of all comments that have a span of at least one character after applying the change. There is no need to raise a changed event for the deleted adornments. The adornments are deleted only if a text change would cause the view to reformat the line and discard the adornments.
        IList<CommentAdornment> keptComments = new List<CommentAdornment>(this.comments.Count);
    
        foreach (CommentAdornment comment in this.comments)
        {
            Span span = comment.Span.GetSpan(e.After);
            //if a comment does not span at least one character, its text was deleted.
            if (span.Length != 0)
            {
                keptComments.Add(comment);
            }
        }
    
        this.comments = keptComments;
    }
    
  9. 为事件添加声明 CommentsChanged

    public event EventHandler<CommentsChangedEventArgs> CommentsChanged;
    
  10. 创建用于 Add() 添加装饰的方法。

    public void Add(SnapshotSpan span, string author, string text)
    {
        if (span.Length == 0)
            throw new ArgumentOutOfRangeException("span");
        if (author == null)
            throw new ArgumentNullException("author");
        if (text == null)
            throw new ArgumentNullException("text");
    
        //Create a comment adornment given the span, author and text.
        CommentAdornment comment = new CommentAdornment(span, author, text);
    
        //Add it to the list of comments. 
        this.comments.Add(comment);
    
        //Raise the changed event.
        EventHandler<CommentsChangedEventArgs> commentsChanged = this.CommentsChanged;
        if (commentsChanged != null)
            commentsChanged(this, new CommentsChangedEventArgs(comment, null));
    }
    
    
  11. 添加方法 RemoveComments()

    public void RemoveComments(SnapshotSpan span)
    {
        EventHandler<CommentsChangedEventArgs> commentsChanged = this.CommentsChanged;
    
        //Get a list of all the comments that are being kept
        IList<CommentAdornment> keptComments = new List<CommentAdornment>(this.comments.Count);
    
        foreach (CommentAdornment comment in this.comments)
        {
            //find out if the given span overlaps with the comment text span. If two spans are adjacent, they do not overlap. To consider adjacent spans, use IntersectsWith. 
            if (comment.Span.GetSpan(span.Snapshot).OverlapsWith(span))
            {
                //Raise the change event to delete this comment. 
                if (commentsChanged != null)
                    commentsChanged(this, new CommentsChangedEventArgs(null, comment));
            }
            else
                keptComments.Add(comment);
        }
    
        this.comments = keptComments;
    }
    
  12. 添加返回GetComments()给定快照范围中的所有注释的方法。

    public Collection<CommentAdornment> GetComments(SnapshotSpan span)
    {
        IList<CommentAdornment> overlappingComments = new List<CommentAdornment>();
        foreach (CommentAdornment comment in this.comments)
        {
            if (comment.Span.GetSpan(span.Snapshot).OverlapsWith(span))
                overlappingComments.Add(comment);
        }
    
        return new Collection<CommentAdornment>(overlappingComments);
    }
    
  13. 添加一个名为CommentsChangedEventArgs>的类,如下所示。

    internal class CommentsChangedEventArgs : EventArgs
    {
        public readonly CommentAdornment CommentAdded;
    
        public readonly CommentAdornment CommentRemoved;
    
        public CommentsChangedEventArgs(CommentAdornment added, CommentAdornment removed)
        {
            this.CommentAdded = added;
            this.CommentRemoved = removed;
        }
    }
    

管理注释装饰

注释装饰管理器创建装饰,并将其添加到装饰层。 它侦 LayoutChanged 听事件, Closed 以便它可以移动或删除装饰。 它还侦 CommentsChanged 听添加或删除注释时注释装饰提供程序触发的事件。

  1. 将类文件添加到 CommentAdornmentTest 项目并将其命名 CommentAdornmentManager

  2. 添加以下 using 指令。

    using System;
    using System.Collections.Generic;
    using System.Windows.Media;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Formatting;
    
  3. 添加名为的 CommentAdornmentManager 的类。

    internal class CommentAdornmentManager
        {
        }
    
  4. 添加一些专用字段。

    private readonly IWpfTextView view;
    private readonly IAdornmentLayer layer;
    private readonly CommentAdornmentProvider provider;
    
  5. 添加一个构造函数,用于订阅管理器LayoutChanged以及ClosedCommentsChanged事件。 构造函数是私有的,因为管理器由静态 Create() 方法实例化。

    private CommentAdornmentManager(IWpfTextView view)
    {
        this.view = view;
        this.view.LayoutChanged += OnLayoutChanged;
        this.view.Closed += OnClosed;
    
        this.layer = view.GetAdornmentLayer("CommentAdornmentLayer");
    
        this.provider = CommentAdornmentProvider.Create(view);
        this.provider.CommentsChanged += OnCommentsChanged;
    }
    
  6. Create()添加获取提供程序或根据需要创建的方法。

    public static CommentAdornmentManager Create(IWpfTextView view)
    {
        return view.Properties.GetOrCreateSingletonProperty<CommentAdornmentManager>(delegate { return new CommentAdornmentManager(view); });
    }
    
  7. 添加 CommentsChanged 处理程序。

    private void OnCommentsChanged(object sender, CommentsChangedEventArgs e)
    {
        //Remove the comment (when the adornment was added, the comment adornment was used as the tag). 
        if (e.CommentRemoved != null)
            this.layer.RemoveAdornmentsByTag(e.CommentRemoved);
    
        //Draw the newly added comment (this will appear immediately: the view does not need to do a layout). 
        if (e.CommentAdded != null)
            this.DrawComment(e.CommentAdded);
    }
    
  8. 添加 Closed 处理程序。

    private void OnClosed(object sender, EventArgs e)
    {
        this.provider.Detach();
        this.view.LayoutChanged -= OnLayoutChanged;
        this.view.Closed -= OnClosed;
    }
    
  9. 添加 LayoutChanged 处理程序。

    private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
    {
        //Get all of the comments that intersect any of the new or reformatted lines of text.
        List<CommentAdornment> newComments = new List<CommentAdornment>();
    
        //The event args contain a list of modified lines and a NormalizedSpanCollection of the spans of the modified lines.  
        //Use the latter to find the comments that intersect the new or reformatted lines of text. 
        foreach (Span span in e.NewOrReformattedSpans)
        {
            newComments.AddRange(this.provider.GetComments(new SnapshotSpan(this.view.TextSnapshot, span)));
        }
    
        //It is possible to get duplicates in this list if a comment spanned 3 lines, and the first and last lines were modified but the middle line was not. 
        //Sort the list and skip duplicates.
        newComments.Sort(delegate(CommentAdornment a, CommentAdornment b) { return a.GetHashCode().CompareTo(b.GetHashCode()); });
    
        CommentAdornment lastComment = null;
        foreach (CommentAdornment comment in newComments)
        {
            if (comment != lastComment)
            {
                lastComment = comment;
                this.DrawComment(comment);
            }
        }
    }
    
  10. 添加用于绘制注释的私有方法。

    private void DrawComment(CommentAdornment comment)
    {
        SnapshotSpan span = comment.Span.GetSpan(this.view.TextSnapshot);
        Geometry g = this.view.TextViewLines.GetMarkerGeometry(span);
    
        if (g != null)
        {
            //Find the rightmost coordinate of all the lines that intersect the adornment.
            double maxRight = 0.0;
            foreach (ITextViewLine line in this.view.TextViewLines.GetTextViewLinesIntersectingSpan(span))
                maxRight = Math.Max(maxRight, line.Right);
    
            //Create the visualization.
            CommentBlock block = new CommentBlock(maxRight, this.view.ViewportRight, g, comment.Author, comment.Text);
    
            //Add it to the layer.
            this.layer.AddAdornment(span, comment, block);
        }
    }
    

使用菜单命令添加注释装饰

可以通过实现 MenuItemCallback VSPackage 的方法,使用菜单命令创建注释装饰。

  1. 将以下引用添加到 MenuCommandTest 项目:

    • Microsoft.VisualStudio.TextManager.Interop

    • Microsoft.VisualStudio.Editor

    • Microsoft.VisualStudio.Text.UI.Wpf

  2. 打开 AddAdornment.cs 文件并添加以下using指令。

    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Editor;
    using CommentAdornmentTest;
    
  3. Execute()删除该方法并添加以下命令处理程序。

    private async void AddAdornmentHandler(object sender, EventArgs e)
    {
    }
    
  4. 添加代码以获取活动视图。 必须获取 SVsTextManager Visual Studio shell 才能激活 IVsTextView

    private async void AddAdornmentHandler(object sender, EventArgs e)
    {
        IVsTextManager txtMgr = (IVsTextManager) await ServiceProvider.GetServiceAsync(typeof(SVsTextManager));
        IVsTextView vTextView = null;
        int mustHaveFocus = 1;
        txtMgr.GetActiveView(mustHaveFocus, null, out vTextView);
    }
    
  5. 如果此文本视图是编辑器文本视图的实例,则可以将其 IVsUserData 强制转换为接口,然后获取 IWpfTextViewHost 及其关联 IWpfTextViewIWpfTextViewHost使用该方法调用Connector.Execute()该方法,该方法获取注释装饰提供程序并添加装饰。 命令处理程序现在应类似于以下代码:

    private async void AddAdornmentHandler(object sender, EventArgs e)
    {
        IVsTextManager txtMgr = (IVsTextManager) await ServiceProvider.GetServiceAsync(typeof(SVsTextManager));
        IVsTextView vTextView = null;
        int mustHaveFocus = 1;
        txtMgr.GetActiveView(mustHaveFocus, null, out vTextView);
        IVsUserData userData = vTextView as IVsUserData;
         if (userData == null)
        {
            Console.WriteLine("No text view is currently open");
            return;
        }
        IWpfTextViewHost viewHost;
        object holder;
        Guid guidViewHost = DefGuidList.guidIWpfTextViewHost;
        userData.GetData(ref guidViewHost, out holder);
        viewHost = (IWpfTextViewHost)holder;
        Connector.Execute(viewHost);
    }
    
  6. 将 AddAdornmentHandler 方法设置为 AddAdornment 构造函数中 AddAdornment 命令的处理程序。

    private AddAdornment(AsyncPackage package, OleMenuCommandService commandService)
    {
        this.package = package ?? throw new ArgumentNullException(nameof(package));
        commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
    
        var menuCommandID = new CommandID(CommandSet, CommandId);
        var menuItem = new MenuCommand(this.AddAdornmentHandler, menuCommandID);
        commandService.AddCommand(menuItem);
    }
    

生成并测试代码

  1. 生成解决方案并开始调试。 应显示实验实例。

  2. 创建文本文件。 键入一些文本,然后选择它。

  3. “工具” 菜单上,单击“ 调用添加装饰”。 气球应显示在文本窗口的右侧,并且应包含类似于以下文本的文本。

    YourUserName

    Fourscore...