Imports System.IO
Imports System.Collections

Namespace AdvancedDemo
	''' <summary>
	''' Implements support for highlighted C# code preview in a RichTextBox
	''' </summary>
	Public Class CSCodeFormatter
		''' <summary>
		''' New class instance
		''' </summary>
		Public Sub New()
			m_arrKeywords = New Hashtable()

			m_arrKeywords.Add("abstract", "")
			m_arrKeywords.Add("event", "")
			m_arrKeywords.Add("new", "")
			m_arrKeywords.Add("struct", "")
			m_arrKeywords.Add("as", "")
			m_arrKeywords.Add("explicit", "")
			m_arrKeywords.Add("null", "")
			m_arrKeywords.Add("switch", "")
			m_arrKeywords.Add("base", "")
			m_arrKeywords.Add("extern", "")
			m_arrKeywords.Add("object", "")
			m_arrKeywords.Add("this", "")
			m_arrKeywords.Add("bool", "")
			m_arrKeywords.Add("false", "")
			m_arrKeywords.Add("operator", "")
			m_arrKeywords.Add("throw", "")
			m_arrKeywords.Add("break", "")
			m_arrKeywords.Add("finally", "")
			m_arrKeywords.Add("out", "")
			m_arrKeywords.Add("true", "")
			m_arrKeywords.Add("byte", "")
			m_arrKeywords.Add("fixed", "")
			m_arrKeywords.Add("override", "")
			m_arrKeywords.Add("try", "")
			m_arrKeywords.Add("case", "")
			m_arrKeywords.Add("float", "")
			m_arrKeywords.Add("params", "")
			m_arrKeywords.Add("typeof", "")
			m_arrKeywords.Add("catch", "")
			m_arrKeywords.Add("for", "")
			m_arrKeywords.Add("private", "")
			m_arrKeywords.Add("uint", "")
			m_arrKeywords.Add("char", "")
			m_arrKeywords.Add("foreach", "")
			m_arrKeywords.Add("protected", "")
			m_arrKeywords.Add("ulong", "")
			m_arrKeywords.Add("checked", "")
			m_arrKeywords.Add("goto", "")
			m_arrKeywords.Add("public", "")
			m_arrKeywords.Add("unchecked", "")
			m_arrKeywords.Add("class", "")
			m_arrKeywords.Add("if", "")
			m_arrKeywords.Add("readonly", "")
			m_arrKeywords.Add("unsafe", "")
			m_arrKeywords.Add("const", "")
			m_arrKeywords.Add("implicit", "")
			m_arrKeywords.Add("ref", "")
			m_arrKeywords.Add("ushort", "")
			m_arrKeywords.Add("continue", "")
			m_arrKeywords.Add("in", "")
			m_arrKeywords.Add("return", "")
			m_arrKeywords.Add("using", "")
			m_arrKeywords.Add("decimal", "")
			m_arrKeywords.Add("int", "")
			m_arrKeywords.Add("sbyte", "")
			m_arrKeywords.Add("virtual", "")
			m_arrKeywords.Add("default", "")
			m_arrKeywords.Add("interface", "")
			m_arrKeywords.Add("sealed", "")
			m_arrKeywords.Add("volatile", "")
			m_arrKeywords.Add("delegate", "")
			m_arrKeywords.Add("internal", "")
			m_arrKeywords.Add("short", "")
			m_arrKeywords.Add("void", "")
			m_arrKeywords.Add("do", "")
			m_arrKeywords.Add("is", "")
			m_arrKeywords.Add("sizeof", "")
			m_arrKeywords.Add("while", "")
			m_arrKeywords.Add("double", "")
			m_arrKeywords.Add("lock", "")
			m_arrKeywords.Add("stackalloc", "")
			m_arrKeywords.Add("else", "")
			m_arrKeywords.Add("long", "")
			m_arrKeywords.Add("static", "")
			m_arrKeywords.Add("enum", "")
			m_arrKeywords.Add("namespace", "")
			m_arrKeywords.Add("string", "")
		End Sub


		#Region "Exposed Functionality"

		''' <summary>
		''' Controls whether to apply CShart highlight
		''' </summary>
		Public Property HighlightCode() As Boolean
			Get
				Return m_bHighlightCode
			End Get
			Set(ByVal value As Boolean)
				m_bHighlightCode = value
			End Set
		End Property

		''' <summary>
		''' Controls whether to collapse the designer generated code
		''' </summary>
		Public Property CollapseDesignerCode() As Boolean
			Get
				Return m_bCollapseDesignerCode
			End Get
			Set(ByVal value As Boolean)
				m_bCollapseDesignerCode = value
			End Set
		End Property

		''' <summary>
		''' Update the edit to show the code for the specified example
		''' </summary>
		''' <param name="sourceEdit"></param>
		''' <param name="leaf"></param>
		Public Sub UpdateSourceEditFromExample(ByVal sourceEdit As RichTextBox, ByVal fileName As String)
			' set wait cursor
			Dim oldCursor As Cursor = Cursor.Current
			Cursor.Current = System.Windows.Forms.Cursors.WaitCursor

			' prepare the source edit
			sourceEdit.Clear()
			sourceEdit.SuspendLayout()
			sourceEdit.Visible = False

			' load the file in the edit
			Dim srRead As StreamReader
			Try
				srRead = New StreamReader(CType(File.OpenRead(fileName), System.IO.Stream), System.Text.Encoding.ASCII)
			Catch
				MessageBox.Show("Failed to load file:" & fileName)
				Return
			End Try

			srRead.BaseStream.Seek(0, SeekOrigin.Begin)
			srRead.BaseStream.Position = 0

			Dim buffer(srRead.BaseStream.Length - 1) As Char
			srRead.Read(buffer, 0, CInt(srRead.BaseStream.Length))

			srRead.DiscardBufferedData()
			srRead.Close()

			sourceEdit.Font = New Font("Courier New", 10)
			sourceEdit.Text = New String(buffer)

			' perform text analysis
			DoCollapseDesignerCode(sourceEdit)
			DoHighlightCode(sourceEdit)

			' resume edit layout
			sourceEdit.Visible = True
			sourceEdit.ResumeLayout()
			Cursor.Current = oldCursor
		End Sub


		#End Region

		#Region "Implementation"

		Private Function IsLineComment(ByVal l As String) As Boolean
			Dim ltrimmed As String = l.TrimStart(Nothing)
			If ltrimmed.Length >= 2 Then
				If ltrimmed.Substring(0, 2) = "//" Then
					Return True
				End If
			End If
			Return False
		End Function

		Private Function IsKeyword(ByVal sWord As String) As Boolean
			Return m_arrKeywords.ContainsKey(sWord)
		End Function


		Private Sub DoCollapseDesignerCode(ByVal sourceEdit As RichTextBox)
			If m_bCollapseDesignerCode = False Then
				Return
			End If

			' highlight keywords
			'string word = "";
			'int wordStart = 0;
			Dim lineStart As Integer = 0
			Dim lineEnd As Integer = 0

			Dim bDesignerCodeFound As Boolean = False
			Dim designerCodeStart As Integer = -1

			' collapse designer code
			lineEnd = sourceEdit.Text.IndexOf(ControlChars.Lf)

			Do While lineEnd <> -1
				Dim line As String = sourceEdit.Text.Substring(lineStart, lineEnd - lineStart + 1)
				Dim lineTrimmed As String = line.Trim()

				If lineTrimmed = "#region Component Designer generated code" Then
					' entering a designer code -> save the the start
					bDesignerCodeFound = True
					designerCodeStart = lineStart
				ElseIf lineTrimmed = "#endregion" AndAlso bDesignerCodeFound Then
					' end region found for designer code -> replace it with a simple string
					bDesignerCodeFound = False
					Dim genCode As String = vbTab & vbTab & "Windows Forms Designer generated code"

					sourceEdit.Text = sourceEdit.Text.Substring(0, designerCodeStart) & genCode & sourceEdit.Text.Substring(lineEnd, sourceEdit.Text.Length - lineEnd)

					sourceEdit.SelectionStart = designerCodeStart
					sourceEdit.SelectionLength = genCode.Length
					sourceEdit.SelectionColor = Color.LightGray
					sourceEdit.SelectionFont = New Font("Arial", 10, FontStyle.Bold)

					lineEnd = designerCodeStart
				End If

				' move to next line
				lineStart = lineEnd + 1
				lineEnd = sourceEdit.Text.IndexOf(ControlChars.Lf, lineStart)
			Loop
		End Sub

		Private Sub DoHighlightCode(ByVal sourceEdit As RichTextBox)
			If m_bHighlightCode = False Then
				Return
			End If

			' highlight keywords
			Dim word As String = ""
			Dim wordStart As Integer = 0
			Dim lineStart As Integer = 0
			Dim lineEnd As Integer = 0

			' highlight
			lineStart = 0
			lineEnd = sourceEdit.Text.IndexOf(ControlChars.Lf)

			Do While lineEnd <> -1
				Dim line As String = sourceEdit.Text.Substring(lineStart, lineEnd - lineStart + 1)

				If IsLineComment(line) Then
					' a comment line
					sourceEdit.SelectionStart = lineStart
					sourceEdit.SelectionLength = lineEnd - lineStart
					sourceEdit.SelectionColor = Color.DarkGreen
				Else
					' parse the line for keywords, inline comments and str constants
					Dim bInStrConst As Boolean = False
					Dim nStrConstStart As Integer = -1

					For i As Integer = 0 To line.Length - 1
						Dim c As Char = line.Chars(i)

						' string constants
						If c = """"c Then
							' end of str constant
							If bInStrConst Then
								sourceEdit.SelectionStart = nStrConstStart + lineStart
								sourceEdit.SelectionLength = i - nStrConstStart + 1
								sourceEdit.SelectionColor = Color.DarkSlateBlue

								bInStrConst = False
							Else
								nStrConstStart = i
								bInStrConst = True
							End If

							Continue For
						End If

						' looking for the end of a string constant
						If bInStrConst Then
							Continue For
						End If

						' inline comment - > comment the rest of the line
						If c = "'"c Then
							sourceEdit.SelectionStart = lineStart + i
							sourceEdit.SelectionLength = lineEnd - lineStart - i
							sourceEdit.SelectionColor = Color.DarkGreen
							Exit For
						End If

						' if c is separator in C# sense
						If c = " "c OrElse c = ControlChars.Tab OrElse c = "("c OrElse c = ")"c OrElse c = ","c OrElse c = "."c OrElse c = ";"c OrElse c = ControlChars.Lf Then
							' if some word was accumulated
							If word.Length <> 0 Then
								If IsKeyword(word) Then
									sourceEdit.SelectionStart = wordStart + lineStart
									sourceEdit.SelectionLength = word.Length
									sourceEdit.SelectionColor = Color.Blue
								End If
							End If

							' clear word
							word = ""
							Continue For
						End If

						If word.Length = 0 Then
							wordStart = i
						End If

						word &= c
					Next i
				End If

				' move to next line
				lineStart = lineEnd + 1
				lineEnd = sourceEdit.Text.IndexOf(ControlChars.Lf, lineStart)
			Loop
		End Sub


		Private m_bHighlightCode As Boolean
		Private m_bCollapseDesignerCode As Boolean
		Private m_arrKeywords As Hashtable

		#End Region
	End Class
End Namespace