Syntax Highlighting classes with example

BlitzMax Forums/BlitzMax Programming/Syntax Highlighting classes with example

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

Yup. After some quick hacking I got an example running very well. Nice clean written classes and example. No optimizations done yet mind you (except where obvious).

I'm thinking about adding it to the sample code section later after you guys have checked it out. (And myself)

A lot of people talked about the speed of the syntax highlighter, and I haven't really tested it with large files.
However, I only update the current line on each keystroke which seems plenty fast on my systems :)

First the example code. Show's how easy it is to use.
Save it as whatever you want. It's your main file.
' LUA Subset Syntax Highlighter usage example

' Include needed stuff
Include "LineTokenizer.bmx"
Include "SyntaxHighLighter.bmx"

' Window setup from MaxGUI tutorials
Local MyWindow:TGadget=CreateWindow("Simple LUA Subset Syntax Highlighting Example", 40,40,400,400,Null,0|WINDOW_TITLEBAR|WINDOW_RESIZABLE)
Global MyText:TGadget=CreateTextArea(0,0,GadgetWidth(MyWindow),GadgetHeight(MyWindow),MyWindow)	
SetGadgetLayout(MyText, 1,1,1,1)

' Create tokenizer
myTokenizer:TLineTokenizer= New TLineTokenizer
myTokenizer.Init(",= ().")

' Setup highlighter
myHighlighter:TSyntaxHighLighter = New TSyntaxHighLighter
myHighLighter.Init(MyText, myTokenizer)
myHighLighter.SetFont("Courier New",12)
myHighLighter.AddKeyword("print")
myHighLighter.AddKeyword("do")
myHighLighter.AddKeyword("for")
myHighLighter.AddKeyword("end")
myHighLighter.AddKeyword("math")
myHighLighter.AddKeyword("random")

' Load from script
myHighLighter.LoadAndColorFromFile("script.txt")

' Current cursor pos
Global cursorPos:Int = 0

' Main loop
Repeat

  WaitEvent()

  Select EventID()

	  Case EVENT_WINDOWCLOSE
	     End
	
	  Case EVENT_GADGETSELECT
	     cursorPos=TextAreaCursor(MyText)

  End Select

  ' Lock for a minor speed boost.
  LockTextArea(MyText)
  myHighlighter.ColorLine(TextAreaLine(MyText, cursorpos))
  UnlockTextArea(MyText)
  
Forever
End


script.txt
for i = 1, 10 do
	print(math.random(6))
end


SyntaxHighlighter.bmx
' Syntax highligher class by Odin Jensen (http://www.furi.dk)
Type TSyntaxHighLighter

	' Internal tokenizer
	Field tokenizer:TLineTokenizer = Null
	
	' Gadget
	Field control:TGadget = Null
	
	' Colors
	Field textR:Byte = 255
	Field textG:Byte = 255
	Field textB:Byte = 50
	
	Field sepR:Byte = 0
	Field sepG:Byte = 255
	Field sepB:Byte = 0
	
	Field numberR:Byte = 0
	Field numberG:Byte = 255
	Field numberB:Byte = 255
	
	Field keywordR:Byte = 255
	Field keywordG:Byte = 255
	Field keywordB:Byte = 255
	
	' Keyword list
	Field keywords:TList = New TList

	
	' Init 
	Method Init(control:TGadget, tokenizer:TLineTokenizer)
	
		' Store for later
		Self.control = control
		Self.tokenizer = tokenizer		
		
		' Set default (or userdefined) colors
		SetBackgroundColor(1,81,107)
		SetTextColor(textR, textG, textB)
		SetSeperatorColor(sepR, sepG, sepB)
		SetNumberColor(numberR, numberG, numberB)
		SetKeywordColor(keywordR, keywordG, keywordB)	
		
		' Set tab size
		SetTextAreaTabs(control, 2)
	
	End Method
	
	' Set background color
	Method SetBackgroundColor(r:Byte, g:Byte, b:Byte)
		
		SetTextAreaColor(control,r,g,b,True)

	End Method
	
	' Set text color
	Method SetTextColor(r:Byte, g:Byte, b:Byte)
	
		SetTextAreaColor(control,r,g,b,False)
		
		textR = r
		textG = g
		textB = b
	
	End Method
	
	' Set seperator
	Method SetSeperatorColor(r:Byte, g:Byte, b:Byte)
	
		sepR = r
		sepG = g
		sepB = b
		
	End Method
	
	' Set number color
	Method SetNumberColor(r:Byte, g:Byte, b:Byte)
	
		numberR = r
		numberG = g
		numberB = b
		
	End Method
	
	' Set keyword color
	Method SetKeywordColor(r:Byte, g:Byte, b:Byte)
	
		keywordR = r
		keywordG = g
		keywordB = b
		
	End Method
	
	' Set font
	Method SetFont(name:String, size:Int)
	
		Local GUIFont:TGuiFont=LoadGuiFont(name, size)
		SetTextAreaFont(control,GUIFont)

	
	End Method
	
	' Add keyword
	Method AddKeyWord(word:String)
	
		keywords.addLast(word)
	
	End Method
	
	' Highlight from file
	Method LoadAndColorFromFile:Int(file:String)
	
		' Read script and add lines
		Local in:TStream=ReadStream(file)
		If (in = Null)
			Return False
		End If
		
		' Lock TextArea
		LockTextArea(control)
		
		' Do file
		Local curLine:Int = 0
		While Not Eof(in)
		    Local line:String = ReadLine(in)
		
			' Add line to control
			AddTextAreaText(control,line+"~n")	

			' Color line
			ColorLine(curLine)			
			
			' Advance line
			curLine:+1
		Wend
		CloseStream(in)
		
		' Unlock TextArea
		UnlockTextArea(control)
		
		' All went well
		Return True
		
	End Method		
	
	' Color line
	Method ColorLine(lineNum:Int)
		
		' Find line pos
		Local curPos:Int = TextAreaChar(control, lineNum)
		
		' Get current line
		Local line:String = TextAreaText(control, lineNum, 1, TEXTAREA_LINES).Replace("~n", "")
			
		' Reset tokenizer
		tokenizer.Reset()
		
		' Tokenize line
		tokenizer.Tokenize(line)
		Local seps:TList = tokenizer.GetSeperators()
	
		' Do seperators
		For Local cs:String = EachIn seps

			For Local i:Int = 0 To line.length-1

				If (Chr(line[i]) = cs)
			
					FormatTextAreaText(control, sepR, sepG, sepB, 0,curPos+i,1)
				
				End If
			Next

		Next	
		
		' Do tokens
		Local tokens:TList = tokenizer.GetTokens()	
		For Local ct:String = EachIn tokens

			If (line.find(ct) <> -1)
		
				If (IsNumber(ct))
					FormatTextAreaText(control, numberR, numberG, numberB, 0,curPos+line.find(ct),ct.length)
				Else If (IsKeyword(ct))
					FormatTextAreaText(control, keywordR, keywordG, keywordB, 0,curPos+line.find(ct),ct.length)
				Else
					FormatTextAreaText(control, textR, textG, textB, 0,curPos+line.find(ct),ct.length)
				End If

			End If
				
		Next	

	
	End Method
	
	' Private IsNumber
	Method IsNumber:Int(in:String)
	
		Return (in = "0" Or in.toInt() <> 0)
	
	End Method
	
	' Private IsKeyword
	Method IsKeyword:Int(in:String)
			
		For Local ck:String = EachIn keywords
					
			If (ck.toLower() = in.toLower())
			
				Return True
			
			End If
		
		Next
		
		Return False
	
	End Method

End Type


LineTokenizer.bmx
' Simple line based tokenizer by Odin Jensen (http://www.furi.dk)
Type TLineTokenizer

	' Token list
	Field tokens:TList = New TList
	
	' Seperator list
	Field seps:TList = New TList
	
	' Seperator search list
	Field sepSearchList:String = " "

	' Init tokenizer
	Method Init(sepList:String)
	
		Reset()
		sepSearchList = sepList
	
	End Method
	
	' Reset tokenizer
	Method Reset()
	
		tokens = New TList
		seps = New TList

	End Method
	
	' Tokenize
	Method Tokenize(line:String)
	
		Local lastSepPos:Int = 0
		For Local i:Int = 0 To line.length-1
		
			If (IsSep(Chr(line[i])))
			
				Local temp:String = line[lastSepPos..i]
				temp = StripIlligalChars(temp)
				If (temp.length > 0)
					tokens.addLast(temp)
				End If
				
				If (seps.Contains(Chr(line[i])) = False And Chr(line[i]) <> " ")				
					seps.addLast(Chr(line[i]))
				End If
				lastSepPos = i+1
			
			End If
					
		Next
		
		' Any leftovers?
		If (lastSepPos < line.length)
		
			tokens.addLast(line[lastSepPos..line.length])
		
		End If
	
	End Method	
	
	' Get tokens
	Method GetTokens:TList()
		
		Return tokens
	
	End Method
	
	' Get non-space seperators
	Method GetSeperators:TList()
	
		Return seps
		
	End Method		
		
	' Private check for seperator function
	Method IsSep:Int(ch:String)
	
		If (sepSearchList.find(ch) <> -1)
		
			Return True		
				
		End If
		
		Return False
	
	End Method
	
	' Private strip illigal chars function
	Method StripIlligalChars:String(in:String)

		Local ret:String = in.replace("~t", "")
		ret = ret.replace("~n", "")
		Return ret	
			
	End Method


End Type

Rem 
' Test
myTokenizer:TLineTokenizer = New TLineTokenizer

myTokenizer.Init(",= ()")
myTokenizer.Tokenize("for i = 0, 10 do")
myTokenizer.Tokenize("~tprint(Dice(6)")
myTokenizer.Tokenize("end")

tokens:TList = myTokenizer.GetTokens()
seps:Tlist = myTokenizer.GetSeperators()

For Local ct:String = EachIn tokens

	Print("Token: " + ct)

Next

Print("~n")

For Local cs:String = EachIn seps

	Print("Seps: " + cs)

Next

End Rem