Browse files

Added preview toolwindow and margin (for activating preview toolwindow).

To do this, the extension also has to include VSPackage elements, so that
it can add a tool window.
  • Loading branch information...
1 parent 446e157 commit 2b79223ff67ffbdb2f3b7608fd7484dcb469a959 Noah Richards committed Jan 10, 2010
View
6 ClassificationFormats.cs → Classifier/ClassificationFormats.cs
@@ -98,9 +98,9 @@ sealed class MarkdownListFormat : ClassificationFormatDefinition
// Code/pre
[Export(typeof(EditorFormatDefinition))]
- [ClassificationType(ClassificationTypeNames = "markdown.code")]
- [Name("markdown.code")]
- [DisplayName("Markdown code block")]
+ [ClassificationType(ClassificationTypeNames = "markdown.block")]
+ [Name("markdown.block")]
+ [DisplayName("Markdown block block")]
[UserVisible(true)]
[Order(Before = Priority.Default, After = "markdown.blockquote")] // Low priority
sealed class MarkdownCodeFormat : ClassificationFormatDefinition
View
2 ClassificationTypes.cs → Classifier/ClassificationTypes.cs
@@ -92,7 +92,7 @@ static class MarkdownClassificationTypes
internal static ClassificationTypeDefinition MarkdownPreDefinition = null;
[Export]
- [Name("markdown.code")]
+ [Name("markdown.block")]
[BaseDefinition("markdown.pre")]
internal static ClassificationTypeDefinition MarkdownCodeDefinition = null;
View
17 Classifier.cs → Classifier/Classifier.cs
@@ -6,6 +6,7 @@
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using System.Text.RegularExpressions;
+using System.Diagnostics;
namespace MarkdownMode
{
@@ -35,20 +36,22 @@ public MarkdownClassifier(ITextBuffer buffer, IClassificationTypeRegistryService
_buffer.Changed += BufferChanged;
}
+ /// <summary>
+ /// When the buffer changes, check to see if any of the edits were in a paragraph with multi-line tokens.
+ /// If so, we need to send out a classification changed event for those paragraphs.
+ /// </summary>
void BufferChanged(object sender, TextContentChangedEventArgs e)
{
- if (e.After != _buffer.CurrentSnapshot)
- return;
-
- ITextSnapshot snapshot = e.After;
+ int eventsSent = 0;
foreach (var change in e.Changes)
{
- SnapshotSpan span = new SnapshotSpan(snapshot, change.NewSpan);
- SnapshotSpan paragraph = GetEnclosingParagraph(span);
+ SnapshotSpan paragraph = GetEnclosingParagraph(new SnapshotSpan(e.After, change.NewSpan));
if (MarkdownParser.ParagraphContainsMultilineTokens(paragraph.GetText()))
{
+ eventsSent++;
+
var temp = this.ClassificationChanged;
if (temp != null)
temp(this, new ClassificationChangedEventArgs(paragraph));
@@ -137,7 +140,7 @@ public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
{ MarkdownParser.TokenType.AutomaticUrl, "markdown.url.automatic" },
{ MarkdownParser.TokenType.Blockquote, "markdown.blockquote" },
{ MarkdownParser.TokenType.Bold, "markdown.bold" },
- { MarkdownParser.TokenType.CodeBlock, "markdown.code" },
+ { MarkdownParser.TokenType.CodeBlock, "markdown.block" },
{ MarkdownParser.TokenType.H1, "markdown.header.h1" },
{ MarkdownParser.TokenType.H2, "markdown.header.h2" },
{ MarkdownParser.TokenType.H3, "markdown.header.h3" },
View
9 ContentType.cs
@@ -5,19 +5,22 @@ namespace MarkdownMode
{
static class ContentType
{
+ public const string Name = "markdown";
+
[Export]
- [Name("markdown")]
+ [Name(Name)]
[DisplayName("Markdown")]
[BaseDefinition("text")]
+ [BaseDefinition("HTML")]
public static ContentTypeDefinition MarkdownModeContentType = null;
[Export]
- [ContentType("markdown")]
+ [ContentType(Name)]
[FileExtension(".mkd")]
public static FileExtensionToContentTypeDefinition MkdFileExtension = null;
[Export]
- [ContentType("markdown")]
+ [ContentType(Name)]
[FileExtension(".markdown")]
public static FileExtensionToContentTypeDefinition MarkdownFileExtension = null;
}
View
164 Margin/Margin.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using Microsoft.VisualStudio.Text;
+using System.Windows.Threading;
+using System.Threading;
+using System.IO;
+using Microsoft.VisualStudio.Shell.Interop;
+
+namespace MarkdownMode
+{
+ sealed class Margin : StackPanel, IWpfTextViewMargin
+ {
+ public const string Name = "MarkdownMargin";
+
+ IWpfTextView textView;
+ ITextDocument document;
+ DispatcherTimer timer;
+ IMarkdownPreviewWindowBroker previewWindowBroker;
+
+ MarkdownSharp.Markdown markdownTransform = new MarkdownSharp.Markdown();
+
+ MarkdownPreviewToolWindow GetPreviewWindow(bool create)
+ {
+ return (previewWindowBroker != null) ? previewWindowBroker.GetMarkdownPreviewToolWindow(create) : null;
+ }
+
+ public Margin(IWpfTextView wpfTextView, IMarkdownPreviewWindowBroker previewWindowBroker, ITextDocument document)
+ {
+ this.textView = wpfTextView;
+ this.previewWindowBroker = previewWindowBroker;
+ this.timer = null;
+ this.document = document;
+
+ textView.GotAggregateFocus += (sender, args) =>
+ {
+ var window = GetPreviewWindow(false);
+ if (window != null)
+ {
+ if (window.CurrentSource == null || window.CurrentSource != this)
+ UpdatePreviewWindow(false);
+ }
+ };
+
+ textView.Closed += (sender, args) => ClearPreviewWindow();
+
+ if (textView.HasAggregateFocus)
+ UpdatePreviewWindow(false);
+
+ textView.TextBuffer.Changed += BufferChanged;
+
+ this.Orientation = System.Windows.Controls.Orientation.Horizontal;
+ this.Background = Brushes.SlateGray;
+ this.Height = 25;
+
+ Button button = new Button() { Content = "Show preview window" };
+ button.Click += (sender, args) =>
+ {
+ if (previewWindowBroker != null)
+ {
+ var window = previewWindowBroker.GetMarkdownPreviewToolWindow(true);
+ ((IVsWindowFrame)window.Frame).Show();
+ }
+ };
+
+ this.Children.Add(button);
+ }
+
+ string GetDocumentName()
+ {
+ if (document == null)
+ return "(no name)";
+ else
+ return Path.GetFileName(document.FilePath);
+ }
+
+ void BufferChanged(object sender, TextContentChangedEventArgs args)
+ {
+ if (timer != null)
+ {
+ timer.Stop();
+ timer = null;
+ }
+
+ timer = new DispatcherTimer(DispatcherPriority.ApplicationIdle)
+ {
+ Interval = TimeSpan.FromMilliseconds(500)
+ };
+
+ timer.Tick += (s, e) =>
+ {
+ if (timer != null)
+ timer.Stop();
+ timer = null;
+
+ UpdatePreviewWindow(async: true);
+ };
+
+ timer.Start();
+ }
+
+ void UpdatePreviewWindow(bool async)
+ {
+ if (async)
+ {
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ string content = markdownTransform.Transform(textView.TextBuffer.CurrentSnapshot.GetText());
+
+ this.Dispatcher.Invoke(new Action(() =>
+ {
+ var previewWindow = GetPreviewWindow(create: true);
+
+ if (previewWindow.CurrentSource == this || previewWindow.CurrentSource == null)
+ previewWindow.SetPreviewContent(this, content, GetDocumentName());
+ }), DispatcherPriority.ApplicationIdle);
+ });
+ }
+ else
+ {
+ var previewWindow = GetPreviewWindow(create: true);
+ if (previewWindow != null)
+ previewWindow.SetPreviewContent(this, markdownTransform.Transform(textView.TextBuffer.CurrentSnapshot.GetText()), GetDocumentName());
+ }
+ }
+
+ void ClearPreviewWindow()
+ {
+ var window = GetPreviewWindow(create: false);
+ if (window != null && window.CurrentSource == this)
+ window.ClearPreviewContent();
+ }
+
+ public FrameworkElement VisualElement
+ {
+ get { return this; }
+ }
+
+ public bool Enabled
+ {
+ get { return true; }
+ }
+
+ public ITextViewMargin GetTextViewMargin(string marginName)
+ {
+ return (marginName == Name) ? this : null;
+ }
+
+ public double MarginSize
+ {
+ get { return this.Height; }
+ }
+
+ public void Dispose()
+ {
+ // Nothing to see here. Move along.
+ }
+ }
+}
View
43 Margin/MarginProvider.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.VisualStudio.Text;
+using System.IO;
+using Microsoft.VisualStudio.Shell.Interop;
+
+namespace MarkdownMode
+{
+ [Export(typeof(IWpfTextViewMarginProvider))]
+ [Name(Margin.Name)]
+ [MarginContainer(PredefinedMarginNames.Top)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ [ContentType(ContentType.Name)]
+ sealed class MarginProvider : IWpfTextViewMarginProvider
+ {
+ [Import]
+ IMarkdownPreviewWindowBrokerService PreviewWindowBrokerService = null;
+
+ [Import]
+ System.IServiceProvider GlobalServiceProvider = null;
+
+ public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer)
+ {
+ ITextDocument document;
+ if (!wpfTextViewHost.TextView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
+ {
+ document = null;
+ }
+
+ // If there is a shell service (which there should be, in VS), force the markdown package to load
+ IVsShell shell = GlobalServiceProvider.GetService(typeof(SVsShell)) as IVsShell;
+ if (shell != null)
+ MarkdownPackage.ForceLoadPackage(shell);
+
+ return new Margin(wpfTextViewHost.TextView, PreviewWindowBrokerService.GetPreviewWindowBroker(), document);
+ }
+ }
+}
View
56 Markdown.cs
@@ -23,7 +23,7 @@
* Copyright (c) 2009 Jeff Atwood
* http://stackoverflow.com
* http://www.codinghorror.com/blog/
- * http://code.google.com/p/markdownsharp/
+ * http://block.google.com/p/markdownsharp/
*
* History: Milan ported the Markdown processor to C#. He granted license to me so I can open source it
* and let the community contribute to and improve MarkdownSharp.
@@ -64,7 +64,7 @@ THE SOFTWARE.
modification, are permitted provided that the following conditions are
met:
-* Redistributions of source code must retain the above copyright notice,
+* Redistributions of source block must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
@@ -243,7 +243,7 @@ static Markdown()
internal const string MarkerUL = @"[*+-]";
internal const string MarkerOL = @"\d+[.]";
- internal static string WholeListRegex = string.Format(@"
+ internal static string WholeListRegex = string.Format(@"
( # $1 = whole list
( # $2
[ ]{{0,{1}}}
@@ -269,23 +269,6 @@ static Markdown()
internal static Regex ListTopLevelRegex = new Regex(@"(?:(?<=\n\n)|\A\n?)" + WholeListRegex,
RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
-
- internal static Regex UlListItemRegex = new Regex(
- @"(\n)? # leading line = $1
- (^[ \t]*) # leading whitespace = $2
- (" + MarkerUL + @") [ \t]+ # list marker = $3
- ((?s:.+?) # list item text = $4
- (\n{1,2}))
- (?= \n* (\z | \2 (" + MarkerUL + @") [ \t]+))", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
-
- internal static Regex OlListItemRegex = new Regex(@"
- (\n)? # leading line = $1
- (^[ \t]*) # leading whitespace = $2
- (" + MarkerOL + @") [ \t]+ # list marker = $3
- ((?s:.+?) # list item text = $4
- (\n{1,2}))
- (?= \n* (\z | \2 (" + MarkerOL + @") [ \t]+))", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
-
// Links
internal static Regex LinkDefRegex = new Regex(string.Format(@"
@@ -459,7 +442,7 @@ static Markdown()
internal static Regex CodeBlockRegex = new Regex(string.Format(@"
(?:\n\n|\A)
- ( # $1 = the code block -- one or more lines, starting with a space/tab
+ ( # $1 = the block block -- one or more lines, starting with a space/tab
(?:
(?:[ ]{{{0}}} | \t) # Lines must start with a tab or a tab-width of spaces
.*\n+
@@ -472,7 +455,7 @@ static Markdown()
internal static Regex CodeSpanRegex = new Regex(@"
(?<!\\) # Character before opening ` can't be a backslash
(`+) # $1 = Opening run of `
- (.+?) # $2 = The code block
+ (.+?) # $2 = The block block
(?<!`)
\1
(?!`)", RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled);
@@ -643,10 +626,9 @@ private static string GetBlockPattern()
private int _listLevel;
-
/// <summary>
/// current version of MarkdownSharp;
- /// see http://code.google.com/p/markdownsharp/ for the latest code or to contribute
+ /// see http://block.google.com/p/markdownsharp/ for the latest block or to contribute
/// </summary>
public string Version
{
@@ -881,7 +863,7 @@ private List<HTMLToken> TokenizeHTML(string text)
/// <summary>
/// Within tags -- meaning between &lt; and &gt; -- encode [\ ` * _] so they
- /// don't conflict with their use in Markdown for code, italics and strong.
+ /// don't conflict with their use in Markdown for block, italics and strong.
/// We're replacing each such character with its corresponding hash
/// value; this is likely overkill, but it should prevent us from colliding
/// with the escape values by accident.
@@ -900,7 +882,7 @@ private string EscapeSpecialCharsWithinTagAttributes(string text)
if (token.Type == HTMLTokenType.Tag)
{
value = value.Replace(@"\", EscapeTable[@"\"]);
- value = Regex.Replace(value, "(?<=.)</?code>(?=.)", EscapeTable[@"`"]);
+ value = Regex.Replace(value, "(?<=.)</?block>(?=.)", EscapeTable[@"`"]);
value = EscapeBoldItalic(value);
}
@@ -1296,7 +1278,7 @@ private string ListItemEvaluator(Match match)
}
/// <summary>
- /// /// Turn Markdown 4-space indented code into HTML pre code blocks
+ /// /// Turn Markdown 4-space indented block into HTML pre block blocks
/// </summary>
private string DoCodeBlocks(string text)
{
@@ -1312,34 +1294,34 @@ private string CodeBlockEvaluator(Match match)
codeBlock = Detab(codeBlock);
codeBlock = _newlinesLeadingTrailing.Replace(codeBlock, "");
- return string.Concat("\n\n<pre><code>", codeBlock, "\n</code></pre>\n\n");
+ return string.Concat("\n\n<pre><block>", codeBlock, "\n</block></pre>\n\n");
}
/// <summary>
- /// Turn Markdown `code spans` into HTML code tags
+ /// Turn Markdown `block spans` into HTML block tags
/// </summary>
private string DoCodeSpans(string text)
{
// * You can use multiple backticks as the delimiters if you want to
- // include literal backticks in the code span. So, this input:
+ // include literal backticks in the block span. So, this input:
//
// Just type ``foo `bar` baz`` at the prompt.
//
// Will translate to:
//
- // <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+ // <p>Just type <block>foo `bar` baz</block> at the prompt.</p>
//
// There's no arbitrary limit to the number of backticks you
// can use as delimters. If you need three consecutive backticks
- // in your code, use four for delimiters, etc.
+ // in your block, use four for delimiters, etc.
//
// * You can use spaces to get literal backticks at the edges:
//
// ... type `` `bar` `` ...
//
// Turns to:
//
- // ... type <code>`bar`</code> ...
+ // ... type <block>`bar`</block> ...
//
return CodeSpanRegex.Replace(text, new MatchEvaluator(CodeSpanEvaluator));
@@ -1352,21 +1334,21 @@ private string CodeSpanEvaluator(Match match)
span = Regex.Replace(span, @"[ \t]*$", ""); // trailing whitespace
span = EncodeCode(span);
- return string.Concat("<code>", span, "</code>");
+ return string.Concat("<block>", span, "</block>");
}
/// <summary>
- /// Encode/escape certain characters inside Markdown code runs.
+ /// Encode/escape certain characters inside Markdown block runs.
/// </summary>
/// <remarks>
- /// The point is that in code, these characters are literals, and lose their
+ /// The point is that in block, these characters are literals, and lose their
/// special Markdown meanings.
/// </remarks>
private string EncodeCode(string code)
{
// Encode all ampersands; HTML entities are not
- // entities within a Markdown code span.
+ // entities within a Markdown block span.
code = code.Replace("&", "&amp;");
// Do the angle bracket song and dance
View
68 MarkdownMode.csproj
@@ -5,15 +5,12 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.20305</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
- <ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<ProjectGuid>{1017287A-83B1-41AB-BAC2-045AC45607C7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MarkdownMode</RootNamespace>
<AssemblyName>MarkdownMode</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- <GeneratePkgDefFile>false</GeneratePkgDefFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -33,40 +30,65 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
- <Reference Include="Microsoft.VisualStudio.CoreUtility">
+ <Reference Include="Microsoft.mshtml, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <EmbedInteropTypes>True</EmbedInteropTypes>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
+ <Reference Include="Microsoft.VisualStudio.CoreUtility">
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <Reference Include="Microsoft.VisualStudio.Shell.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+ <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+ <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <EmbedInteropTypes>False</EmbedInteropTypes>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.VisualStudio.Text.Data">
- <Private>False</Private>
+ <Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Text.Logic">
- <Private>False</Private>
+ <Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Text.UI">
- <Private>False</Private>
+ <Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Text.UI.Wpf">
- <Private>False</Private>
+ <Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
- <Reference Include="Microsoft.CSharp" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
- <Compile Include="ClassificationFormats.cs" />
- <Compile Include="ClassificationTypes.cs" />
- <Compile Include="Classifier.cs" />
+ <Compile Include="Classifier\ClassificationFormats.cs" />
+ <Compile Include="Classifier\ClassificationTypes.cs" />
+ <Compile Include="Classifier\Classifier.cs" />
<Compile Include="ContentType.cs" />
+ <Compile Include="Margin\Margin.cs" />
+ <Compile Include="Margin\MarginProvider.cs" />
<Compile Include="Markdown.cs" />
<Compile Include="MarkdownParser.cs" />
+ <Compile Include="Resources.Designer.cs">
+ <DependentUpon>Resources.resx</DependentUpon>
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ </Compile>
+ <Compile Include="ToolWindow\Guids.cs" />
+ <Compile Include="ToolWindow\MarkdownPreviewWindowBrokerService.cs" />
+ <Compile Include="ToolWindow\MarkdownPackage.cs" />
+ <Compile Include="ToolWindow\MarkdownPreviewToolWindow.cs" />
</ItemGroup>
<ItemGroup>
<None Include="source.extension.vsixmanifest">
@@ -76,6 +98,26 @@
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
+ <ItemGroup>
+ <VSCTCompile Include="ToolWindow\MarkdownPackage.vsct">
+ <ResourceName>1000</ResourceName>
+ </VSCTCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Resources\Images_24bit.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ <MergeWithCTO>true</MergeWithCTO>
+ </EmbeddedResource>
+ </ItemGroup>
+ <PropertyGroup>
+ <RegisterOutputPackage>true</RegisterOutputPackage>
+ <RegisterWithCodebase>true</RegisterWithCodebase>
+ </PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
View
144 MarkdownParser.cs
@@ -30,19 +30,41 @@ static class MarkdownParser
{
internal const char _dummyChar = '~';
+ // These match the regexes that Markdown.cs uses, *except* that they don't have a lookahead at the end for
+ // either another list item or the end of the string. Since we both a) don't strip out multiple endlines at the end
+ // of the list, and b) don't actually care which list a given list element belongs to, we can just get rid of that.
+
+ static Regex UlListItemRegex = new Regex(
+ @"(\n)? # leading line = $1
+ (^[ \t]*) # leading whitespace = $2
+ (" + Markdown.MarkerUL + @") [ \t]+ # list marker = $3
+ ((?s:.+?) # list item text = $4
+ (\n{1,}))", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
+
+ static Regex OlListItemRegex = new Regex(@"
+ (\n)? # leading line = $1
+ (^[ \t]*) # leading whitespace = $2
+ (" + Markdown.MarkerOL + @") [ \t]+ # list marker = $3
+ ((?s:.+?) # list item text = $4
+ (\n{1,}))", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
+
#region Markdown public parser interface
/// <summary>
- /// Parses the given Markdown-formatted paragraph (a series of non-blank lines).
+ /// Parses the given Markdown-formatted paragraph into tokens.
/// </summary>
/// <param name="text">The paragraph of text to parse</param>
+ /// <param name="offset">An optional offset that all the generated tokens will use.</param>
/// <returns>An enumeration of tokens parsed from the text.</returns>
public static IEnumerable<Token> ParseMarkdownParagraph(string text, int offset = 0)
{
List<Token> tokens = new List<Token>();
+ if (text.Trim().Length == 0)
+ return tokens;
+
// First, write over the html tags with dummy characters so we ignore them
- text = BlankOutHtmlTags(text);
+ text = DestroyHtmlTags(text);
// Parse the paragraph into parts. Note that the text will be modified in each step, but
// characters will not be added or removed (so that the token locations as offsets into the text
@@ -70,7 +92,10 @@ public static bool ParagraphContainsMultilineTokens(string text)
return false;
}
-
+
+ /// <summary>
+ /// Markdown token types.
+ /// </summary>
public enum TokenType
{
// Bold/italics
@@ -112,6 +137,9 @@ public enum TokenType
HorizontalRule,
}
+ /// <summary>
+ /// A Markdown token, which is a Span in the given text and an associated token type.
+ /// </summary>
public struct Token
{
public Token(TokenType type, Span span) { TokenType = type; Span = span; }
@@ -122,30 +150,49 @@ public struct Token
#endregion
- #region Parser methods
+ #region Parser methods (private)
+
+ static IEnumerable<Token> ParseSpans(ref string text, int offset = 0)
+ {
+ List<Token> tokens = new List<Token>();
+
+ tokens.AddRange(ParseCodeSpans(ref text, offset));
+
+ // Make sure we don't parse backslash-escaped pieces of the text
+ text = DestroyBackslashEscapes(text);
+
+ tokens.AddRange(ParseImages(ref text, offset));
+ tokens.AddRange(ParseAnchors(ref text, offset));
+ tokens.AddRange(ParseItalicsAndBold(ref text, offset));
+
+ return tokens;
+ }
static IEnumerable<Token> ParseCodeBlocks(ref string text, int offset = 0)
{
List<Token> tokens = new List<Token>();
text = Markdown.CodeBlockRegex.Replace(text, match =>
{
- string codeBlock = match.Groups[1].Value;
-
tokens.Add(new Token(TokenType.CodeBlock, SpanFromGroup(match.Groups[1])));
- return DestroyCodeBlock(codeBlock);
+ return DestroyMarkdownCharsInBlock(match.Value);
});
return tokens;
}
- static Regex MagicMarkdownCharRegex = new Regex(@"[\*_{}[\]]", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
-
- static string DestroyCodeBlock(string code)
+ static IEnumerable<Token> ParseCodeSpans(ref string text, int offset = 0)
{
- // Destroy characters that are magic in markdown
- return MagicMarkdownCharRegex.Replace(code, new string(_dummyChar, 1));
+ List<Token> tokens = new List<Token>();
+
+ text = Markdown.CodeSpanRegex.Replace(text, match =>
+ {
+ tokens.Add(new Token(TokenType.CodeBlock, SpanFromGroup(match.Groups[2], offset)));
+ return DestroyMarkdownCharsInBlock(match.Value);
+ });
+
+ return tokens;
}
static IEnumerable<Token> ParseHorizontalRules(ref string text, int offset = 0)
@@ -190,7 +237,6 @@ static IEnumerable<Token> ParseBlockQuotes(ref string text, int offset = 0)
return tokens;
}
-
static IEnumerable<Token> ParseHeaders(ref string text, int offset = 0)
{
List<Token> tokens = new List<Token>();
@@ -232,7 +278,9 @@ static IEnumerable<Token> ParseLists(ref string text, int offset = 0, int listLe
{
List<Token> tokens = new List<Token>();
- MatchEvaluator evaluator = match =>
+ Regex regex = (listLevel == 0) ? Markdown.ListTopLevelRegex : Markdown.ListNestedRegex;
+
+ text = regex.Replace(text, match =>
{
TokenType type = Regex.IsMatch(match.Groups[3].Value, Markdown.MarkerUL) ?
TokenType.UnorderedListElement : TokenType.OrderedListElement;
@@ -248,19 +296,14 @@ static IEnumerable<Token> ParseLists(ref string text, int offset = 0, int listLe
return match.Value;
else
return new string(_dummyChar, match.Length);
- };
-
- if (listLevel == 0)
- text = Markdown.ListTopLevelRegex.Replace(text, evaluator);
- else
- text = Markdown.ListNestedRegex.Replace(text, evaluator);
+ });
return tokens;
}
static IEnumerable<Token> ParseListItems(ref string list, int listLevel, TokenType listType, int offset = 0)
{
- Regex regex = (listType == TokenType.OrderedListElement) ? Markdown.OlListItemRegex : Markdown.UlListItemRegex;
+ Regex regex = (listType == TokenType.OrderedListElement) ? OlListItemRegex : UlListItemRegex;
List<Token> tokens = new List<Token>();
@@ -269,18 +312,18 @@ static IEnumerable<Token> ParseListItems(ref string list, int listLevel, TokenTy
// Add a token for the list bullet (like * or 1.)
tokens.Add(new Token(listType, SpanFromGroup(match.Groups[3], offset)));
- // Parse recursively:
string item = match.Groups[4].Value;
string leadingLine = match.Groups[1].Value;
int matchOffset = match.Groups[4].Index + offset;
if (!String.IsNullOrEmpty(leadingLine) || Regex.IsMatch(item, @"\n{2,}"))
{
- // This is kinda rough - we're going to trim each line and re-parse them as paragraphs
+ // This is kinda rough - we're going to trim each line and re-parse them as paragraphs.
+ // This should work for everything but the two-line header format, which we can't have here anyways (I don't think)
foreach (var line in Markdown._entireLines.Matches(item).Cast<Match>())
{
- Match strip = Regex.Match(item, @"^(\t|[ ]{1," + Markdown.TabWidth + @"})(.*)$", RegexOptions.Singleline);
+ Match strip = Regex.Match(line.Value, @"^(\t|[ ]{1," + Markdown.TabWidth + @"})?(.*)$", RegexOptions.Singleline);
if (strip.Success)
{
@@ -295,49 +338,17 @@ static IEnumerable<Token> ParseListItems(ref string list, int listLevel, TokenTy
{
// recursion for sub-lists
tokens.AddRange(ParseLists(ref item, matchOffset, listLevel + 1));
- tokens.AddRange(ParseSpans(ref item, matchOffset));;
+
+ // Just in case, parse spans on the segment as well. If ParseLists found sublists, it would
+ // have blanked them out, so this won't do anything in that case. If there weren't sublists,
+ // though, this will take care of subexpressions.
+ tokens.AddRange(ParseSpans(ref item, matchOffset));
}
});
return tokens;
}
- /// <summary>
- /// Parses the spans in the given text, using the offset provided (as the text is generally
- /// a substring of the text we started with).
- /// </summary>
- static IEnumerable<Token> ParseSpans(ref string text, int offset = 0)
- {
- List<Token> tokens = new List<Token>();
-
- // Code spans
- tokens.AddRange(ParseCodeSpans(ref text, offset));
-
- text = DestroyBackslashEscapes(text);
-
- // For images and anchors, we want to both parse the tokens and replace tokens with junk, since
- // it shouldn't be further parsed.
- tokens.AddRange(ParseImages(ref text, offset));
- tokens.AddRange(ParseAnchors(ref text, offset));
-
- // Removed DoAutoLinks here, since the default editor URL logic should find them
-
- // Bold and italics
- tokens.AddRange(ParseItalicsAndBold(ref text, offset));
-
- return tokens;
- }
-
- static IEnumerable<Token> ParseCodeSpans(ref string text, int offset = 0)
- {
- List<Token> tokens = new List<Token>();
-
- foreach (var match in Markdown.CodeSpanRegex.Matches(text).Cast<Match>())
- tokens.Add(new Token(TokenType.CodeBlock, SpanFromGroup(match.Groups[2], offset)));
-
- return tokens;
- }
-
static IEnumerable<Token> ParseImages(ref string text, int offset = 0)
{
List<Token> tokens = new List<Token>();
@@ -411,9 +422,9 @@ static IEnumerable<Token> ParseItalicsAndBold(ref string text, int offset = 0)
#endregion
- #region Little helpers for markdown parsing logic
+ #region Helpers
- static string BlankOutHtmlTags(string text)
+ static string DestroyHtmlTags(string text)
{
int pos = 0;
int tagStart = 0;
@@ -437,6 +448,13 @@ static string BlankOutHtmlTags(string text)
return newText.ToString();
}
+ static Regex MagicMarkdownCharRegex = new Regex(@"[\*_{}[\]]", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
+ static string DestroyMarkdownCharsInBlock(string block)
+ {
+ // Destroy characters that are magic in markdown
+ return MagicMarkdownCharRegex.Replace(block, new string(_dummyChar, 1));
+ }
+
static string DestroyBackslashEscapes(string text)
{
// All the backslash strings are two characters long
View
88 Resources.Designer.cs
@@ -0,0 +1,88 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.21006.1
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MarkdownMode {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MarkdownMode.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static System.Drawing.Bitmap _301 {
+ get {
+ object obj = ResourceManager.GetObject("301", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Can not create tool window..
+ /// </summary>
+ internal static string CanNotCreateWindow {
+ get {
+ return ResourceManager.GetString("CanNotCreateWindow", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Markdown Preview Window.
+ /// </summary>
+ internal static string ToolWindowTitle {
+ get {
+ return ResourceManager.GetString("ToolWindowTitle", resourceCulture);
+ }
+ }
+ }
+}
View
130 Resources.resx
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="CanNotCreateWindow" xml:space="preserve">
+ <value>Can not create tool window.</value>
+ </data>
+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <data name="301" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>Resources\Images_24bit.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+ </data>
+ <data name="ToolWindowTitle" xml:space="preserve">
+ <value>Markdown Preview Window</value>
+ </data>
+</root>
View
BIN Resources/Images_24bit.bmp
Binary file not shown.
View
18 ToolWindow/Guids.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace MarkdownMode
+{
+ static class GuidList
+ {
+ public const string guidMarkdownPackagePkgString = "06fa6fbc-681e-4fdc-bd58-81e8401c5e00";
+ public const string guidMarkdownPackageCmdSetString = "36c5243f-48c6-4666-bc21-8c398b312f0f";
+ public const string guidToolWindowPersistanceString = "acd82a5f-9c35-400b-b9d0-f97925f3b312";
+
+ public static readonly Guid guidMarkdownPackageCmdSet = new Guid(guidMarkdownPackageCmdSetString);
+ };
+
+ static class PkgCmdId
+ {
+ public const int cmdidMarkdownPreviewWindow = 0x0101;
+ }
+}
View
98 ToolWindow/MarkdownPackage.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.ComponentModel.Design;
+using Microsoft.Win32;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.ComponentModelHost;
+using Microsoft.VisualStudio;
+
+namespace MarkdownMode
+{
+ [PackageRegistration(UseManagedResourcesOnly = true)]
+ [InstalledProductRegistration("#110", "#112", "1.0")]
+ [ProvideMenuResource(1000, 1)]
+ [ProvideToolWindow(typeof(MarkdownPreviewToolWindow))]
+ [Guid(GuidList.guidMarkdownPackagePkgString)]
+ public sealed class MarkdownPackage : Package, IMarkdownPreviewWindowBroker
+ {
+ public static bool ForceLoadPackage(IVsShell shell)
+ {
+ Guid packageGuid = new Guid(GuidList.guidMarkdownPackagePkgString);
+ IVsPackage package;
+
+ int hr = shell.IsPackageLoaded(ref packageGuid, out package);
+
+ if (hr == VSConstants.S_OK)
+ return true;
+ else
+ return ErrorHandler.Succeeded(shell.LoadPackage(ref packageGuid, out package));
+ }
+
+ public MarkdownPackage()
+ {
+ Trace.WriteLine("Loaded MarkdownPackage.");
+ }
+
+ private void ShowToolWindow(object sender, EventArgs e)
+ {
+ var window = GetToolWindow(true);
+
+ if (window != null)
+ {
+ IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
+ Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
+ }
+ }
+
+ internal MarkdownPreviewToolWindow GetToolWindow(bool create)
+ {
+ var window = this.FindToolWindow(typeof(MarkdownPreviewToolWindow), 0, create);
+ if ((null == window) || (null == window.Frame))
+ {
+ return null;
+ }
+
+ return window as MarkdownPreviewToolWindow;
+ }
+
+ #region Package Members
+
+ /// <summary>
+ /// Initialization of the package; this method is called right after the package is sited, so this is the place
+ /// where you can put all the initilaization code that rely on services provided by VisualStudio.
+ /// </summary>
+ protected override void Initialize()
+ {
+ Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
+ base.Initialize();
+
+ // Add our command handlers for menu (commands must exist in the .vsct file)
+ IMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as IMenuCommandService;
+ if (mcs != null)
+ {
+ // Create the command for the tool window
+ CommandID toolwndCommandID = new CommandID(GuidList.guidMarkdownPackageCmdSet, PkgCmdId.cmdidMarkdownPreviewWindow);
+ MenuCommand menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
+ mcs.AddCommand( menuToolWin );
+ }
+
+ IComponentModel componentModel = GetService(typeof(SComponentModel)) as IComponentModel;
+ if (componentModel != null)
+ {
+ var brokerService = componentModel.GetService<IMarkdownPreviewWindowBrokerService>();
+ if (brokerService != null)
+ brokerService.RegisterPreviewWindowBroker(this);
+ }
+ }
+ #endregion
+
+ public MarkdownPreviewToolWindow GetMarkdownPreviewToolWindow(bool create)
+ {
+ return GetToolWindow(create);
+ }
+ }
+}
View
94 ToolWindow/MarkdownPackage.vsct
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+ <!-- This is the file that defines the actual layout and type of the commands.
+ It is divided in different sections (e.g. command definition, command
+ placement, ...), with each defining a specific set of properties.
+ See the comment before each section for more details about how to
+ use it. -->
+
+ <!-- The VSCT compiler (the tool that translates this file into the binary
+ format that VisualStudio will consume) has the ability to run a preprocessor
+ on the vsct file; this preprocessor is (usually) the C++ preprocessor, so
+ it is possible to define includes and macros with the same syntax used
+ in C++ files. Using this ability of the compiler here, we include some files
+ defining some of the constants that we will use inside the file. -->
+
+ <!--This is the file that defines the IDs for all the commands exposed by VisualStudio. -->
+ <Extern href="stdidcmd.h"/>
+
+ <!--This header contains the command ids for the menus provided by the shell. -->
+ <Extern href="vsshlids.h"/>
+
+ <!--Definition of some VSCT specific constants. In this sample we use it for the IDs inside the guidOfficeIcon group. -->
+ <Extern href="msobtnid.h"/>
+
+
+
+
+ <!--The Commands section is where we the commands, menus and menu groups are defined.
+ This section uses a Guid to identify the package that provides the command defined inside it. -->
+ <Commands package="guidMarkdownPackagePkg">
+ <!-- Inside this section we have different sub-sections: one for the menus, another
+ for the menu groups, one for the buttons (the actual commands), one for the combos
+ and the last one for the bitmaps used. Each element is identified by a command id that
+ is a unique pair of guid and numeric identifier; the guid part of the identifier is usually
+ called "command set" and is used to group different command inside a logically related
+ group; your package should define its own command set in order to avoid collisions
+ with command ids defined by other packages. -->
+
+
+
+ <!--Buttons section. -->
+ <!--This section defines the elements the user can interact with, like a menu command or a button
+ or combo box in a toolbar. -->
+ <Buttons>
+ <!--To define a menu group you have to specify its ID, the parent menu and its display priority.
+ The command is visible and enabled by default. If you need to change the visibility, status, etc, you can use
+ the CommandFlag node.
+ You can add more than one CommandFlag node e.g.:
+ <CommandFlag>DefaultInvisible</CommandFlag>
+ <CommandFlag>DynamicVisibility</CommandFlag>
+ If you do not want an image next to your command, remove the Icon node or set it to <Icon guid="guidOfficeIcon" id="msotcidNoIcon" /> -->
+
+
+ <Button guid="guidMarkdownPackageCmdSet" id="cmdidMarkdownPreviewWindow" priority="0x0101" type="Button">
+ <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1"/>
+ <!--<Icon guid="guidImages" id="bmpPic2" />-->
+ <Icon guid="guidOfficeIcon" id="msotcidNoIcon"/>
+ <Strings>
+ <CommandName>cmdidMarkdownPreviewWindow</CommandName>
+ <ButtonText>&amp;Markdown Preview Window</ButtonText>
+ </Strings>
+ </Button>
+
+ </Buttons>
+
+ <!--The bitmaps section is used to define the bitmaps that are used for the commands.-->
+ <!--<Bitmaps>
+
+ <Bitmap guid="guidImages" href="Resources\Images_24bit.bmp" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
+
+ </Bitmaps>-->
+
+ </Commands>
+
+ <Symbols>
+ <!-- This is the package guid. -->
+ <GuidSymbol name="guidMarkdownPackagePkg" value="{06fa6fbc-681e-4fdc-bd58-81e8401c5e00}" />
+
+ <!-- This is the guid used to group the menu commands together -->
+ <GuidSymbol name="guidMarkdownPackageCmdSet" value="{36c5243f-48c6-4666-bc21-8c398b312f0f}">
+ <IDSymbol name="cmdidMarkdownPreviewWindow" value="0x0101" />
+ </GuidSymbol>
+
+ <GuidSymbol name="guidImages" value="{4fa980d8-e34f-4456-bddc-ce863534a48d}" >
+ <IDSymbol name="bmpPic1" value="1" />
+ <IDSymbol name="bmpPic2" value="2" />
+ <IDSymbol name="bmpPicSearch" value="3" />
+ <IDSymbol name="bmpPicX" value="4" />
+ <IDSymbol name="bmpPicArrows" value="5" />
+ </GuidSymbol>
+ </Symbols>
+
+</CommandTable>
View
102 ToolWindow/MarkdownPreviewToolWindow.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Windows;
+using System.Runtime.InteropServices;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.Shell;
+using System.Windows.Controls;
+
+namespace MarkdownMode
+{
+ [Guid("acd82a5f-9c35-400b-b9d0-f97925f3b312")]
+ public class MarkdownPreviewToolWindow : ToolWindowPane
+ {
+ private WebBrowser browser;
+
+ const string EmptyWindowHtml = "Open a markdown file to see a preview.";
+ int? scrollBackTo = null;
+
+ /// <summary>
+ /// Standard constructor for the tool window.
+ /// </summary>
+ public MarkdownPreviewToolWindow() : base(null)
+ {
+ this.Caption = "Markdown Preview";
+ this.BitmapResourceID = 301;
+ this.BitmapIndex = 1;
+
+ browser = new WebBrowser();
+ browser.NavigateToString(EmptyWindowHtml);
+ browser.LoadCompleted += (sender, args) =>
+ {
+ if (scrollBackTo.HasValue)
+ {
+ var document = browser.Document as mshtml.IHTMLDocument2;
+
+ if (document != null)
+ {
+ var element = document.body as mshtml.IHTMLElement2;
+ if (element != null)
+ {
+ element.scrollTop = scrollBackTo.Value;
+ }
+ }
+ }
+
+ scrollBackTo = null;
+ };
+ }
+
+ public override object Content
+ {
+ get { return browser; }
+ }
+
+ bool isVisible = false;
+ public bool IsVisible
+ {
+ get { return isVisible; }
+ }
+
+ public override void OnToolWindowCreated()
+ {
+ isVisible = true;
+ }
+
+ public void SetPreviewContent(object source, string html, string title)
+ {
+ this.Caption = "Markdown Preview - " + title;
+ scrollBackTo = null;
+
+ if (source == CurrentSource)
+ {
+ var document = browser.Document as mshtml.IHTMLDocument2;
+ if (document != null)
+ {
+ var element = document.body as mshtml.IHTMLElement2;
+ if (element != null)
+ {
+ scrollBackTo = element.scrollTop;
+ }
+ }
+ }
+
+ CurrentSource = source;
+ browser.NavigateToString(html);
+ }
+
+ public void ClearPreviewContent()
+ {
+ this.Caption = "Markdown Preview";
+ scrollBackTo = null;
+ CurrentSource = null;
+
+ browser.NavigateToString(EmptyWindowHtml);
+ }
+
+ public object CurrentSource { get; private set; }
+ }
+}
View
35 ToolWindow/MarkdownPreviewWindowBrokerService.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ComponentModel.Composition;
+
+namespace MarkdownMode
+{
+ interface IMarkdownPreviewWindowBrokerService
+ {
+ void RegisterPreviewWindowBroker(IMarkdownPreviewWindowBroker broker);
+ IMarkdownPreviewWindowBroker GetPreviewWindowBroker();
+ }
+
+ interface IMarkdownPreviewWindowBroker
+ {
+ MarkdownPreviewToolWindow GetMarkdownPreviewToolWindow(bool create);
+ }
+
+ [Export(typeof(IMarkdownPreviewWindowBrokerService))]
+ sealed class MarkdownPreviewWindowBrokerService : IMarkdownPreviewWindowBrokerService
+ {
+ IMarkdownPreviewWindowBroker broker;
+
+ public void RegisterPreviewWindowBroker(IMarkdownPreviewWindowBroker broker)
+ {
+ this.broker = broker;
+ }
+
+ public IMarkdownPreviewWindowBroker GetPreviewWindowBroker()
+ {
+ return broker;
+ }
+ }
+}
View
2 source.extension.vsixmanifest
@@ -6,6 +6,7 @@
<Version>0.1</Version>
<Description>A language service for editing Markdown files.</Description>
<Locale>1033</Locale>
+ <InstalledByMsi>false</InstalledByMsi>
<SupportedProducts>
<VisualStudio Version="10.0">
<Edition>VST_All</Edition>
@@ -17,5 +18,6 @@
<References />
<Content>
<MefComponent>|MarkdownMode|</MefComponent>
+ <VsPackage>|MarkdownMode;PkgdefProjectOutputGroup|</VsPackage>
</Content>
</Vsix>

0 comments on commit 2b79223

Please sign in to comment.