322 lines
11 KiB
VB.net
322 lines
11 KiB
VB.net
#Region "Microsoft.VisualBasic::1e764ec984dcf0e82eba480da40bc8a6, Microsoft.VisualBasic.Core\ApplicationServices\Terminal\MarkdownRender\MarkdownRender.vb"
|
|
|
|
' Author:
|
|
'
|
|
' asuka (amethyst.asuka@gcmodeller.org)
|
|
' xie (genetics@smrucc.org)
|
|
' xieguigang (xie.guigang@live.com)
|
|
'
|
|
' Copyright (c) 2018 GPL3 Licensed
|
|
'
|
|
'
|
|
' GNU GENERAL PUBLIC LICENSE (GPL3)
|
|
'
|
|
'
|
|
' This program is free software: you can redistribute it and/or modify
|
|
' it under the terms of the GNU General Public License as published by
|
|
' the Free Software Foundation, either version 3 of the License, or
|
|
' (at your option) any later version.
|
|
'
|
|
' This program is distributed in the hope that it will be useful,
|
|
' but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
' GNU General Public License for more details.
|
|
'
|
|
' You should have received a copy of the GNU General Public License
|
|
' along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
' /********************************************************************************/
|
|
|
|
' Summaries:
|
|
|
|
' Class MarkdownRender
|
|
'
|
|
' Constructor: (+1 Overloads) Sub New
|
|
'
|
|
' Function: bufferAllIs, bufferIs, DefaultStyleRender
|
|
'
|
|
' Sub: applyGlobal, DoParseSpans, DoPrint, EndSpan, Print
|
|
' PrintSpans, Reset, restoreStyle, WalkChar
|
|
'
|
|
'
|
|
' /********************************************************************************/
|
|
|
|
#End Region
|
|
|
|
Imports System.Runtime.CompilerServices
|
|
Imports Microsoft.VisualBasic.Language
|
|
Imports Microsoft.VisualBasic.Language.Default
|
|
Imports Microsoft.VisualBasic.Text
|
|
Imports Microsoft.VisualBasic.Text.Parser
|
|
|
|
Namespace ApplicationServices.Terminal
|
|
|
|
''' <summary>
|
|
''' A simple markdown render on console
|
|
''' </summary>
|
|
''' <remarks>
|
|
''' 主要是渲染下面的一些元素:
|
|
'''
|
|
''' + code: 红色
|
|
''' + url: 蓝色
|
|
''' + blockquote: 灰色背景色
|
|
''' </remarks>
|
|
Public Class MarkdownRender
|
|
|
|
Shared ReadOnly defaultTheme As [Default](Of MarkdownTheme) = New MarkdownTheme With {
|
|
.[Global] = Nothing,
|
|
.BlockQuote = (ConsoleColor.Black, ConsoleColor.Gray),
|
|
.CodeBlock = (ConsoleColor.Red, ConsoleColor.Yellow),
|
|
.InlineCodeSpan = (ConsoleColor.Red, ConsoleColor.Black),
|
|
.Url = (ConsoleColor.Blue, ConsoleColor.Black),
|
|
.Bold = (ConsoleColor.Black, ConsoleColor.Yellow),
|
|
.Italy = (ConsoleColor.Yellow, ConsoleColor.DarkGray),
|
|
.HeaderSpan = (ConsoleColor.DarkGreen, ConsoleColor.Yellow)
|
|
}
|
|
|
|
Dim theme As MarkdownTheme
|
|
Dim markdown As CharPtr
|
|
Dim indent As Integer
|
|
|
|
Dim initialGlobal As ConsoleFontStyle
|
|
|
|
Private Sub New(theme As MarkdownTheme)
|
|
Me.theme = theme
|
|
Me.initialGlobal = New ConsoleFontStyle With {
|
|
.BackgroundColor = Console.BackgroundColor,
|
|
.ForeColor = Console.ForegroundColor
|
|
}
|
|
End Sub
|
|
|
|
Public Sub DoPrint(markdown$, indent%)
|
|
Me.markdown = markdown.LineTokens.JoinBy(ASCII.LF)
|
|
Me.indent = indent
|
|
Me.applyGlobal()
|
|
Me.Reset()
|
|
Me.DoParseSpans()
|
|
Me.PrintSpans()
|
|
End Sub
|
|
|
|
Private Sub DoParseSpans()
|
|
Do While Not markdown.EndRead
|
|
Call WalkChar(++markdown)
|
|
Loop
|
|
|
|
If textBuf > 0 Then
|
|
Call EndSpan(False)
|
|
End If
|
|
End Sub
|
|
|
|
Dim boldSpan As Boolean = False
|
|
Dim inlineCodespan As Boolean = False
|
|
Dim blockquote As Boolean = False
|
|
Dim italySpan As Boolean = False
|
|
Dim headerSpan As Boolean = False
|
|
Dim lastNewLine As Boolean = True
|
|
Dim controlBuf As New List(Of Char)
|
|
Dim textBuf As New List(Of Char)
|
|
|
|
Friend styleStack As New Stack(Of ConsoleFontStyle)
|
|
Friend currentStyle As ConsoleFontStyle
|
|
|
|
Dim spans As New List(Of Span)
|
|
|
|
Public Sub Reset()
|
|
blockquote = False
|
|
boldSpan = False
|
|
inlineCodespan = False
|
|
lastNewLine = True
|
|
italySpan = False
|
|
headerSpan = False
|
|
controlBuf *= 0
|
|
textBuf *= 0
|
|
styleStack.Clear()
|
|
spans *= 0
|
|
End Sub
|
|
|
|
''' <summary>
|
|
''' <see cref="controlBuf"/> is in given string pattern?
|
|
''' </summary>
|
|
''' <param name="term"></param>
|
|
''' <returns></returns>
|
|
Private Function bufferIs(term As String) As Boolean
|
|
If controlBuf <> term.Length Then
|
|
Return False
|
|
Else
|
|
Return controlBuf.SequenceEqual(term)
|
|
End If
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' All of the character value in <see cref="controlBuf"/> is equals to given character <paramref name="c"/>
|
|
''' </summary>
|
|
''' <param name="c"></param>
|
|
''' <returns></returns>
|
|
Private Function bufferAllIs(c As Char) As Boolean
|
|
If controlBuf = 0 Then
|
|
Return False
|
|
Else
|
|
Return controlBuf.All(Function(b) b = c)
|
|
End If
|
|
End Function
|
|
|
|
Private Sub restoreStyle()
|
|
If styleStack.Count > 0 Then
|
|
Call styleStack.Pop()
|
|
End If
|
|
|
|
If styleStack.Count = 0 Then
|
|
Call applyGlobal()
|
|
Else
|
|
Call styleStack.Peek.SetConfig(Me)
|
|
End If
|
|
End Sub
|
|
|
|
Private Sub applyGlobal()
|
|
If theme.Global Is Nothing Then
|
|
Call initialGlobal.SetConfig(Me)
|
|
Else
|
|
Call theme.Global.SetConfig(Me)
|
|
End If
|
|
End Sub
|
|
|
|
Private Sub PrintSpans()
|
|
Dim isNewLine As Boolean = True
|
|
|
|
For Each span As Span In spans
|
|
If isNewLine Then
|
|
Console.CursorLeft = indent
|
|
End If
|
|
|
|
span.Print()
|
|
isNewLine = span.IsEndByNewLine
|
|
Next
|
|
|
|
Call applyGlobal()
|
|
Call Console.WriteLine()
|
|
End Sub
|
|
|
|
Private Sub EndSpan(byNewLine As Boolean)
|
|
Dim text As String = textBuf.CharString
|
|
Dim style As ConsoleFontStyle = currentStyle.Clone
|
|
|
|
If text.StartsWith("((http(s)?)|(ftp))[:]//", RegexICSng) Then
|
|
style = theme.Url
|
|
End If
|
|
|
|
If styleStack.Count > 0 AndAlso styleStack.Peek.Equals(theme.CodeBlock) Then
|
|
style.BackgroundColor = theme.CodeBlock.BackgroundColor
|
|
End If
|
|
|
|
If text.Length > 0 Then
|
|
spans += New Span With {
|
|
.style = style,
|
|
.text = text,
|
|
.IsEndByNewLine = byNewLine
|
|
}
|
|
textBuf *= 0
|
|
End If
|
|
End Sub
|
|
|
|
Private Sub WalkChar(c As Char)
|
|
Select Case c
|
|
Case "`"c
|
|
controlBuf += c
|
|
lastNewLine = False
|
|
Case "*"c
|
|
controlBuf += c
|
|
lastNewLine = False
|
|
Case ASCII.LF
|
|
lastNewLine = True
|
|
blockquote = False
|
|
headerSpan = False
|
|
textBuf += ASCII.LF
|
|
EndSpan(True)
|
|
restoreStyle()
|
|
Case ">"c
|
|
If lastNewLine AndAlso controlBuf = 0 Then
|
|
controlBuf += c
|
|
ElseIf lastNewLine AndAlso bufferIs(">") Then
|
|
' 空白的blockquote行
|
|
Else
|
|
textBuf += c
|
|
End If
|
|
lastNewLine = False
|
|
Case " "c
|
|
lastNewLine = False
|
|
|
|
If controlBuf = 1 AndAlso controlBuf(Scan0) = ">"c Then
|
|
blockquote = True
|
|
controlBuf *= 0
|
|
theme.BlockQuote.SetConfig(Me)
|
|
textBuf += " "c
|
|
textBuf += " "c
|
|
ElseIf headerSpan AndAlso bufferAllIs("#"c) Then
|
|
theme.HeaderSpan.SetConfig(Me)
|
|
controlBuf *= 0
|
|
Else
|
|
EndSpan(False)
|
|
textBuf += c
|
|
EndSpan(False)
|
|
End If
|
|
Case "#"c
|
|
If lastNewLine AndAlso (controlBuf = 0 OrElse controlBuf.All(Function(x) x = "#"c)) Then
|
|
controlBuf += c
|
|
headerSpan = True
|
|
ElseIf headerSpan AndAlso textBuf = 0 Then
|
|
controlBuf += c
|
|
Else
|
|
textBuf += c
|
|
End If
|
|
|
|
lastNewLine = False
|
|
Case Else
|
|
lastNewLine = False
|
|
|
|
If bufferIs("``") Then
|
|
If inlineCodespan Then
|
|
' 结束栈
|
|
EndSpan(False)
|
|
inlineCodespan = False
|
|
restoreStyle()
|
|
Else
|
|
EndSpan(False)
|
|
inlineCodespan = True
|
|
theme.InlineCodeSpan.SetConfig(Me)
|
|
End If
|
|
|
|
controlBuf *= 0
|
|
textBuf += c
|
|
ElseIf bufferIs("**") Then
|
|
If boldSpan Then
|
|
EndSpan(False)
|
|
boldSpan = False
|
|
restoreStyle()
|
|
Else
|
|
EndSpan(False)
|
|
boldSpan = True
|
|
theme.Bold.SetConfig(Me)
|
|
End If
|
|
|
|
controlBuf *= 0
|
|
textBuf += c
|
|
Else
|
|
textBuf += c
|
|
End If
|
|
End Select
|
|
End Sub
|
|
|
|
<MethodImpl(MethodImplOptions.AggressiveInlining)>
|
|
Public Shared Sub Print(markdown As String, Optional theme As MarkdownTheme = Nothing, Optional indent% = 0)
|
|
Call New MarkdownRender(theme Or defaultTheme).DoPrint(markdown, indent)
|
|
End Sub
|
|
|
|
<MethodImpl(MethodImplOptions.AggressiveInlining)>
|
|
Public Shared Function DefaultStyleRender() As MarkdownRender
|
|
Return New MarkdownRender(defaultTheme)
|
|
End Function
|
|
End Class
|
|
End Namespace
|