Code archives/BlitzPlus Gui/CodeArea Gadget

This code has been declared by its author to be Public Domain code.

Download source code

CodeArea Gadget by JoshK2011
This should work 100% correctly. The syntax highlighter will support BlitzMax, Lua, and C, with multi-line comments. Lua double and single-quote strings are supported.

GO TO THE BOTTOM OF THIS THREAD FOR THE MOST RECENT VERSION
SuperStrict

Import maxgui.drivers
Import maxgui.proxygadgets

'Codearea style constants
Const CODEAREA_SYNTAXHIGHLIGHTING:Int=1
Const CODEAREA_CANUNDO:Int=2
Const CODEAREA_CORRECTCASE:Int=4
Const CODEAREA_READONLY:Int=8
Const CODEAREA_DEFAULT:Int=CODEAREA_SYNTAXHIGHLIGHTING|CODEAREA_CANUNDO|CODEAREA_CORRECTCASE

'Codearea format constants
Const CODEAREA_PLAIN:Int=0
Const CODEAREA_STRING:Int=1
Const CODEAREA_COMMENT:Int=2
Const CODEAREA_KEYWORD:Int=3
Const CODEAREA_PREPROCESSOR:Int=4

'CodeArea language constants
Const CODEAREA_C:Int=0
Const CODEAREA_LUA:Int=1
Const CODEAREA_BMX:Int=2

Private

Type TAction
	
	Field add:String
	Field addpos:Int
	Field addlen:Int
	Field remove:String
	Field removepos:Int
	Field removelen:Int
	Field beforepos:Int
	Field beforelen:Int
	Field afterpos:Int
	Field afterlen:Int
	
	Method Undo(codearea:TCodeArea)
		Local n:Int
		
		SetTextAreaText codearea,remove,addpos,addlen
		SelectTextAreaText codearea,beforepos,beforelen
		If (CODEAREA_SYNTAXHIGHLIGHTING & codearea.style)
			LockTextArea codearea.textarea
			For n=TextAreaLine(codearea.textarea,beforepos) To TextAreaLine(codearea.textarea,beforepos+beforelen)
				If codearea.FormatLine(n) Exit
			Next
			UnlockTextArea codearea.textarea
		EndIf
		EmitEvent CreateEvent(EVENT_GADGETSELECT,codearea,addpos-remove.length)
	EndMethod
	
	Method Redo(codearea:TCodeArea)
		Local n:Int

		SetTextAreaText codearea,add,removepos,removelen
		SelectTextAreaText codearea,afterpos,afterlen
		If (CODEAREA_SYNTAXHIGHLIGHTING & codearea.style)
			LockTextArea codearea.textarea
			For n=TextAreaLine(codearea.textarea,afterpos) To TextAreaLine(codearea.textarea,afterpos+afterlen)
				If codearea.FormatLine(n) Exit
			Next
			UnlockTextArea codearea.textarea
		EndIf
		EmitEvent CreateEvent(EVENT_GADGETSELECT,codearea,removepos+add.length)
	EndMethod
	
EndType

Type TFormat
	
	Field r:Int
	Field g:Int
	Field b:Int
	Field style:Int
	
	Function Create:TFormat(r:Int,g:Int,b:Int,style:Int)
		Local format:TFormat=New TFormat
		format.r=r
		format.g=g
		format.b=b
		format.style=style
		Return format
	EndFunction
	
EndType

Public

Type TCodeArea Extends TProxyGadget
	
	Global dummywindow:TGadget' used to hold dummy textarea
	Global dummytextarea:TGadget' used for paste operations
	Global maxundolevels:Int=0' set to 0 to disable limit
	
	Field textarea:TGadget
	Field selpos:Int
	Field sellen:Int
	Field text:String
	Field undoposition:Int=-1
	Field actions:TAction[]
	Field format:TFormat[6]
	Field needsreformat:Int
	Field locked:Int
	Field keywords:TMap=New TMap
	Field token_comment:String="//"
	Field token_multilinecommentbegin:String="/*"
	Field token_multilinecommentend:String="*/"
	Field token_string:String="~q"
	Field token_string2:String' Lua uses multiple string tokens
	Field token_preprocessor:String="#"
	Field multilinecommentstyle:Int
	Field lineincommentblock:Byte[]
	Field multilinecommentschanged:Int
	
	Method New()
		format[CODEAREA_PLAIN]=TFormat.Create(0,0,0,0)
		format[CODEAREA_STRING]=TFormat.Create(128,0,128,0)
		format[CODEAREA_COMMENT]=TFormat.Create(0,128,0,0)
		format[CODEAREA_PREPROCESSOR]=TFormat.Create(255,0,0,0)
		format[CODEAREA_KEYWORD]=TFormat.Create(0,0,255,0)
	EndMethod
	
	Method Cleanup()
		RemoveHook EmitEventHook,EventHook,Self
	EndMethod
	
	'---------------------------------------------------------------------------------------------
	'Syntax highlighting
	'---------------------------------------------------------------------------------------------	
	Method SetLanguage(language:Int)
		Select language
		
		Case CODEAREA_C
			token_comment="//"
			token_multilinecommentbegin="/*"
			token_multilinecommentend="*/"
			token_string="~q"
			token_string2=""
			token_preprocessor="#"			
			multilinecommentstyle=0
		
		Case CODEAREA_LUA
			token_comment="--"
			token_multilinecommentbegin="--[["
			token_multilinecommentend="]]--"
			token_string="~q"
			token_string2="'"
			token_preprocessor=""
			multilinecommentstyle=0
		
		Case CODEAREA_BMX
			token_comment="'"
			token_multilinecommentbegin="Rem"
			token_multilinecommentend="EndRem"
			token_string="~q"
			token_string2=""
			token_preprocessor="?"
			multilinecommentstyle=1
		
		EndSelect
		
		FormatAll()
	EndMethod
	
	Method LockText()
		locked=True
		textarea.LockText()
	EndMethod
	
	Method UnlockText()
		textarea.UnlockText()
		locked=False
		If needsreformat FormatAll()
	EndMethod
	
	Method SetFont(font:TGUIFont)
		Local n:Int
		
		format[CODEAREA_PLAIN].style=FontStyle(font)
		textarea.SetFont(font)
		FormatAll()
	EndMethod
	
	Method SetTextColor(r:Int,g:Int,b:Int)
		Local n:Int
		
		format[CODEAREA_PLAIN].r=r
		format[CODEAREA_PLAIN].g=g
		format[CODEAREA_PLAIN].b=b
		FormatAll()
	EndMethod
	
	Method AddKeyword(word:String)
		keywords.insert(word.tolower(),word)
	EndMethod
	
	Method ClearKeywords()
		keywords.Clear()
	EndMethod
	
	Method KeywordExists:Int(word:String)
		Local keyword:String
		keyword=String(keywords.valueforkey(word.tolower()))
		If keyword
			If Not (CODEAREA_CORRECTCASE & style)
				If keyword=word
					Return True
				Else
					Return False
				EndIf
			Else
				Return True
			EndIf
		Else
			Return False
		EndIf
	EndMethod
	
	Method SetFormat(element:Int,r:Int,g:Int,b:Int,style:Int)
		format[element]=TFormat.Create(r,g,b,style)
		FormatAll()
	EndMethod
	
	Field dontupdatemultilinecomments:Int
	
	Method FormatAll()
		Print "FORMATALL"
		Local n:Int
		Local multilinecommentbegun:Int
		For n=0 To lineincommentblock.length-1
			lineincommentblock[n]=0
		Next
		
		dontupdatemultilinecomments=True
		If locked
			needsreformat=True
		Else
			LockTextArea textarea
			For n=0 To TextAreaLen(textarea,TEXTAREA_LINES)-1
				If FormatLine(n) Exit
			Next
			UnlockTextArea textarea
			needsreformat=False			
		EndIf
		dontupdatemultilinecomments=False
	EndMethod
	
	Method CharacterInQuotes:Int(s:String,c:Int)
		Local n:Int
		Local char:String
		Local stringopen:Int
		Local string2open:Int
		Local stringopenpos:Int
		Local string2openpos:Int
		
		For n=0 To c
			char=Chr(s[n])
			Select char
			Case token_string
				stringopen=Not stringopen
				stringopenpos=n
			Case token_string2
				If token_string2<>token_string
					string2open=Not string2open
					string2openpos=n
				EndIf
			EndSelect
		Next
		If stringopen
			If s.Find(token_string,stringopenpos+1)>-1 Return True
		EndIf
		If string2open
			If s.Find(token_string2,string2openpos+1)>-1 Return True
		EndIf
	EndMethod
	
	Rem
	Method FormatMultiLineComments()
		Local p0:Int=-1,p1:Int,s:String
		
		s=TextAreaText(textarea)
		Repeat
			p0=s.Find(token_multilinecommentblockbegin,p0+1)
			If p0>-1
				If Not CharacterInQuotes(s,p0)
					p1=s.Find(token_multilinecommentblockend,p0+1)
					If p1>-1
						If Not CharacterInQuotes(s,p0)
							FormatTextAreaText textarea,format[FORMAT_COMMENT].r,format[FORMAT_COMMENT].g,format[FORMAT_COMMENT].b,format[FORMAT_COMMENT].style,p0,p1-p0
						EndIf
					Else
						Exit
					EndIf
				EndIf
			Else
				Exit
			EndIf
		Forever
		
	EndMethod
	EndRem
	
	Method FormatLine:Int(line:Int,multilinecommentbegun:Int=False)
		Local s:String=TextAreaText(textarea,line,1,TEXTAREA_LINES)
		Local pos:Int,p2:Int
		Local l:Int
		Local word:String
		Local c:String
		Local stringopen:Int
		Local n:Int
		Local word2:String
		Local stringreplacement:String
		Local commentbegun:Int
		Local multilinecommentstate:Int
		
		'Print line+", "+TextAreaLen(textarea,TEXTAREA_LINES)
		'If line>TextAreaLen(textarea,TEXTAREA_LINES) Return False
		
		
		If line>lineincommentblock.length-1
			lineincommentblock=lineincommentblock[..line+1]
		EndIf

		multilinecommentstate=lineincommentblock[line]

		
		'If Not multilinecommentbegun
		'	If lineincommentblock[line]>1 multilinecommentbegun=True
		'EndIf
		
		'If previous line is commented, so is this one
		If line>0
			If lineincommentblock[line-1]=1 Or lineincommentblock[line-1]=2
				lineincommentblock[line]=2
			'Else
			'	lineincommentblock[line]=0
			EndIf
		EndIf
		
		If token_string2="" token_string2=token_string
		
		FormatTextAreaText textarea,format[CODEAREA_PLAIN].r,format[CODEAREA_PLAIN].g,format[CODEAREA_PLAIN].b,format[CODEAREA_PLAIN].style,line,1,TEXTAREA_LINES
		
		pos=-1
		
		'Remove multi-line comments

			'Rem
			If lineincommentblock[line]=2
				lineincommentblock[line]=0
				
				If multilinecommentstyle=1
					If (CODEAREA_CORRECTCASE & style)
						p2=s.tolower().Find(token_multilinecommentend.tolower(),pos+1)
					Else
						p2=s.Find(token_multilinecommentend,pos+1)
					EndIf
				Else
					p2=s.Find(token_multilinecommentend,pos+1)
				EndIf
				
				'Print s+", "+token_multilinecommentend
				'If p2>-1 End
				
				'BlitzMax does not allow multi-line comment begin/end tokens in the middle of a string
				If p2>-1
					If multilinecommentstyle=1
						If (CODEAREA_CORRECTCASE & style)
							If s.Trim().tolower()<>token_multilinecommentend.tolower()
								p2=-1
							Else
								If s.Trim()<>token_multilinecommentend SetTextAreaText textarea,token_multilinecommentend,TextAreaChar(textarea,line)+p2,token_multilinecommentend.length
							EndIf
						Else
							If s.Trim()<>token_multilinecommentend p2=-1
						EndIf
					EndIf
				EndIf
				
				If CharacterInQuotes(s,p2) p2=-1
				If p2>-1
					stringreplacement=""
					For n=pos To p2+token_multilinecommentend.length-1
						FormatTextAreaText textarea,format[CODEAREA_COMMENT].r,format[CODEAREA_COMMENT].g,format[CODEAREA_COMMENT].b,format[CODEAREA_COMMENT].style,TextAreaChar(textarea,line)+n,1,TEXTAREA_CHARS
						stringreplacement:+"|"
					Next
					lineincommentblock[line]=3' terminates comment block
					s=s[..pos]+stringreplacement+s[(p2+token_multilinecommentend.length-1)..]
				Else
					FormatTextAreaText textarea,format[CODEAREA_COMMENT].r,format[CODEAREA_COMMENT].g,format[CODEAREA_COMMENT].b,format[CODEAREA_COMMENT].style,line,1,TEXTAREA_LINES
					lineincommentblock[line]=2' inside comment block
					'Print s
					s=""
				EndIf
			EndIf
			'EndRem
			
			If lineincommentblock[line]=1 lineincommentblock[line]=0
			pos=-1
			Repeat
				If multilinecommentstyle=1
					If (CODEAREA_CORRECTCASE & style)
						pos=s.tolower().Find(token_multilinecommentbegin.tolower(),pos+1)
					Else
						pos=s.Find(token_multilinecommentbegin,pos+1)					
					EndIf
				Else
					pos=s.Find(token_multilinecommentbegin,pos+1)	
				EndIf
				
				'BlitzMax does not allow multi-line comment begin/end tokens in the middle of a string
				If pos>-1
					If multilinecommentstyle=1
						If (CODEAREA_CORRECTCASE & style)
							If s.Trim().tolower()<>token_multilinecommentbegin.tolower()
								pos=-1
							Else
								If s.Trim()<>token_multilinecommentbegin SetTextAreaText textarea,token_multilinecommentbegin,TextAreaChar(textarea,line)+pos,token_multilinecommentbegin.length
							EndIf
						Else
							If s.Trim()<>token_multilinecommentbegin pos=-1
						EndIf
					EndIf
				EndIf
				
				If pos>-1
					If Not CharacterInQuotes(s,pos)
						p2=s.Find(token_multilinecommentend,pos+1)
						If CharacterInQuotes(s,p2) p2=-1
						If p2>-1
							stringreplacement=""
							For n=pos To p2+token_multilinecommentend.length-1
								FormatTextAreaText textarea,format[CODEAREA_COMMENT].r,format[CODEAREA_COMMENT].g,format[CODEAREA_COMMENT].b,format[CODEAREA_COMMENT].style,TextAreaChar(textarea,line)+n,1,TEXTAREA_CHARS
								stringreplacement:+"|"
							Next
							s=s[..pos]+stringreplacement+s[(p2+token_multilinecommentend.length-1)..]
							lineincommentblock[line]=0' comment block is contained within this line
						Else
							l=s.length-pos'-token_multilinecommentbegin.length
							s=s[..pos]
							'Print l+", "+s
							'pos=TextAreaChar(textarea,line)+pos
							FormatTextAreaText textarea,format[CODEAREA_COMMENT].r,format[CODEAREA_COMMENT].g,format[CODEAREA_COMMENT].b,format[CODEAREA_COMMENT].style,TextAreaChar(textarea,line)+pos,l,TEXTAREA_CHARS
							lineincommentblock[line]=1' begins comment block
							'commentbegun=True
						EndIf
					EndIf
				Else
					Exit
				EndIf
			Forever
		
		'Remove trailing comments
		pos=-1
		Repeat
			pos=s.Find(token_comment,pos+1)
			If pos>-1
				If Not CharacterInQuotes(s,pos)
					l=s.length-pos+1-token_comment.length
					s=s[..pos]
					FormatTextAreaText textarea,format[CODEAREA_COMMENT].r,format[CODEAREA_COMMENT].g,format[CODEAREA_COMMENT].b,format[CODEAREA_COMMENT].style,TextAreaChar(textarea,line)+pos,l,TEXTAREA_CHARS
					Exit
				EndIf
			Else
				Exit
			EndIf
		Forever
		
		'Format strings
		pos=-1
		pos=s.Find(token_string,pos+1)
		While pos>-1
			p2=s.Find(token_string,pos+1)
			If p2>-1
				FormatTextAreaText textarea,format[CODEAREA_STRING].r,format[CODEAREA_STRING].g,format[CODEAREA_STRING].b,format[CODEAREA_STRING].style,TextAreaChar(textarea,line)+pos,p2-pos+1,TEXTAREA_CHARS
				'pos=s.Find(token_string,p2+1)
				stringreplacement=""
				For n=pos To p2
					stringreplacement:+"|"
				Next
				Local pl:Int=s.length
				s=s[..pos]+stringreplacement+s[(p2+1)..]
				'Notify s.length+", "+pl
			Else
				Exit
			EndIf
			pos=s.Find(token_string,pos+1)
		Wend
		
		'Lua uses two string tokens
		Rem
		If token_string2<>token_string
			pos=s.Find(token_string2)
			While pos>-1
				p2=s.Find(token_string2,pos+1)
				If p2>-1
					FormatTextAreaText textarea,format[CODEAREA_STRING].r,format[CODEAREA_STRING].g,format[CODEAREA_STRING].b,format[CODEAREA_STRING].style,TextAreaChar(textarea,line)+pos,p2-pos+1,TEXTAREA_CHARS
					pos=s.Find(token_string2,p2+1)
					stringreplacement=""
					For n=pos To p2
						stringreplacement:+"|"
					Next
					s=s[..pos]+stringreplacement+s[(p2+1)..]
				Else
					Exit
				EndIf			
			Wend		
		EndIf
		EndRem
				
		'Remove defines
		If token_preprocessor
			pos=s.Trim().Find(token_preprocessor)
			If pos=0
				pos=s.Find(token_preprocessor)
				l=s.length-pos+1-token_preprocessor.length
				s=s[..pos]
				pos=TextAreaChar(textarea,line)+pos
				FormatTextAreaText textarea,format[CODEAREA_PREPROCESSOR].r,format[CODEAREA_PREPROCESSOR].g,format[CODEAREA_PREPROCESSOR].b,format[CODEAREA_PREPROCESSOR].style,pos,l,TEXTAREA_CHARS
			EndIf
		EndIf
		
		'Add this in case this is the last line of the text
		If Right(s,1).Trim()<>"" s=s+"~n"
		
		'Format keywords
		For n=0 To s.length-1
			c=Chr(s[n])
			Select c.Trim()
			Case "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9","_"
				If Not stringopen
					word:+c
				EndIf
			Case token_string,token_string2
				stringopen=CharacterInQuotes(s,n)
				'word=""
				'stringopen=Not stringopen
			Default
				If Not stringopen
					If word
						If KeywordExists(word)
							If (CODEAREA_CORRECTCASE & style)
								word2=String(keywords.valueforkey(word.tolower()))
								If word<>word2
									'Print word2
									SetTextAreaText textarea,word2,TextAreaChar(textarea,line)+n-word.length,word.length
								EndIf
							EndIf	
							FormatTextAreaText textarea,format[CODEAREA_KEYWORD].r,format[CODEAREA_KEYWORD].r,format[CODEAREA_KEYWORD].b,format[CODEAREA_KEYWORD].style,TextAreaChar(textarea,line)+n-word.length,word.length,TEXTAREA_CHARS	
						EndIf
					EndIf
				EndIf
				word=""
			EndSelect
		Next
		
		If multilinecommentstate<>lineincommentblock[line]
			If Not dontupdatemultilinecomments
				Print "CHANGE"
				FormatAll()
				Return True
			EndIf
		EndIf
		
		Return False
	EndMethod
	
	'---------------------------------------------------------------------------------------------
	'Undo/redo functionality
	'---------------------------------------------------------------------------------------------
	Method ClearUndos()
		If (CODEAREA_CANUNDO & style)
			actions=Null
			undoposition=-1
		EndIf
	EndMethod
	
	Method AddAction(action:TAction)
		If (CODEAREA_CANUNDO & style)
			undoposition:+1
			actions=actions[..undoposition+1]
			actions[undoposition]=action
			If maxundolevels>0
				If undoposition>maxundolevels
					undoposition:-1
					actions=actions[1..]
				EndIf
			EndIf
		EndIf
	EndMethod
	
	Method CanUndo:Int()
		If (CODEAREA_CANUNDO & style)
			If undoposition>-1 Return True		
		EndIf
	EndMethod
	
	Method CanRedo:Int()
		If (CODEAREA_CANUNDO & style)
			If undoposition<=actions.length-2 Return True
		EndIf
	EndMethod
	
	Method Undo()
		If CanUndo()
			actions[undoposition].Undo(Self)
			undoposition:-1
		EndIf
	EndMethod
	
	Method Redo()
		If CanRedo()
			actions[undoposition+1].Redo(Self)		
			undoposition:+1
		EndIf
	EndMethod
		
	'---------------------------------------------------------------------------------------------
	'Event handling
	'---------------------------------------------------------------------------------------------
	Method ProcessEvent:Int(event:TEvent)
		Select event.source
		Case Self,textarea
			Select event.id
			
			Case EVENT_GADGETACTION
				text=TextAreaText(textarea)
				selpos=TextAreaCursor(textarea,TEXTAREA_CHARS)
				sellen=TextAreaSelLen(textarea,TEXTAREA_CHARS)
				
			Case EVENT_GADGETSELECT
				selpos=TextAreaCursor(textarea,TEXTAREA_CHARS)
				sellen=TextAreaSelLen(textarea,TEXTAREA_CHARS)
			
			EndSelect
		EndSelect
		Return True
	EndMethod
	
	Method SetText(text:String)
		Local n:Int
		
		textarea.SetText(text)
		If (CODEAREA_SYNTAXHIGHLIGHTING & style)
			LockTextArea textarea
			For n=0 To TextAreaLen(textarea,TEXTAREA_LINES)
				If FormatLine(n) Exit
			Next
			UnlockTextArea textarea
		EndIf
	EndMethod
	
	Method ReplaceText(pos:Int,count:Int,text:String,units:Int)
		Local n:Int
		
		textarea.ReplaceText(pos,count,text,units)
		If (CODEAREA_SYNTAXHIGHLIGHTING & style)
			LockTextArea textarea
			If units=TEXTAREA_CHARS
				For n=TextAreaLine(textarea,pos) To TextAreaLine(textarea,pos+count)
					If FormatLine(n) Exit
				Next
			Else
				For n=pos To pos+count
					If FormatLine(n) Exit
				Next				
			EndIf
			UnlockTextArea textarea
		EndIf
	EndMethod	
	
	Method Activate(command:Int)
		Local n:Int
		Local clipboardtext:String
		
		Select command
		Case ACTIVATE_PASTE
			SetTextAreaText dummytextarea,""
			GadgetPaste(dummytextarea)
			ActivateGadget textarea
			clipboardtext=TextAreaText(dummytextarea)
			
			Update()
			Local action:TAction
			action=New TAction
			AddAction action
			action.add=clipboardtext
			action.addpos=selpos
			action.addlen=clipboardtext.length
			action.beforepos=selpos
			action.beforelen=sellen
			action.remove=Mid(text,selpos+1,sellen)
			action.removepos=selpos
			action.removelen=sellen
			action.afterpos=selpos+clipboardtext.length
			action.afterlen=0
			'action.afterpos=selpos
			'action.afterlen=clipboardtext.length	
			action.Redo(Self)
			EmitEvent CreateEvent(EVENT_GADGETACTION,Self)
			If (CODEAREA_SYNTAXHIGHLIGHTING & style)
				LockTextArea textarea
				'Notify TextAreaLine(textarea,action.afterpos)+", "+TextAreaLine(textarea,action.afterpos+action.afterlen)
				For n=TextAreaLine(textarea,action.beforepos) To TextAreaLine(textarea,action.beforepos+clipboardtext.length)
					If FormatLine(n) Exit
				Next
				UnlockTextArea textarea
			EndIf
			
		Case ACTIVATE_CUT
			If sellen
				textarea.activate(ACTIVATE_COPY)
				Filter(CreateEvent(EVENT_KEYCHAR,textarea,8))
			EndIf
		Default
			textarea.activate(command)
		EndSelect
	EndMethod
	
	Method Update()
		selpos=TextAreaCursor(textarea,TEXTAREA_CHARS)
		sellen=TextAreaSelLen(textarea,TEXTAREA_CHARS)
		text=TextAreaText(textarea)
	EndMethod
	
	Method Filter:Int(event:TEvent)
		Local action:TAction
		
		Select event.id
		Case EVENT_KEYDOWN
			Select event.data
			Case KEY_UP,KEY_DOWN,KEY_RIGHT,KEY_LEFT
				Return True
			EndSelect
		Case EVENT_KEYCHAR
			If MODIFIER_COMMAND & event.mods Return False
			If MODIFIER_OPTION & event.mods Return False
			Update()
			If event.data=8
				action=New TAction
				AddAction action
				action.add=""
				action.addlen=0
				action.beforepos=selpos
				action.beforelen=sellen
				If sellen=0
					action.addpos=selpos-1
					action.afterpos=selpos-1
					action.afterlen=0
					action.removelen=1
					action.removepos=selpos-1
					action.remove=Mid(text,1+selpos-action.removelen,action.removelen)
				Else
					action.addpos=selpos
					action.afterpos=selpos
					action.afterlen=0
					action.removepos=selpos
					action.removelen=sellen
					action.remove=Mid(text,1+selpos,sellen)
				EndIf
				action.Redo(Self)	
				EmitEvent CreateEvent(EVENT_GADGETACTION,Self)
				'action.Undo(self)' test to make sure this is reversable
			ElseIf event.data>31 And event.data<127 Or event.data=13
				action=New TAction
				AddAction action
				action.add=Chr(event.data)
				action.addpos=selpos
				action.addlen=1
				action.beforepos=selpos
				action.beforelen=sellen
				action.remove=Mid(text,selpos+1,sellen)
				action.removepos=selpos
				action.removelen=sellen
				action.afterpos=selpos+1
				action.afterlen=0
				action.Redo(Self)
				EmitEvent CreateEvent(EVENT_GADGETACTION,Self)
				'action.Undo(self)' test to make sure this is reversable
			EndIf
		EndSelect
		Return False
	EndMethod
	
	Function Callback:Int(event:TEvent,context:Object)
		Local codearea:TCodeArea=New TCodeArea
		codearea=TCodeArea(context)
		If codearea
			Return codearea.Filter(event)
		EndIf
	EndFunction
	
	Function EventHook:Object(id:Int,data:Object,context:Object)
		Local event:TEvent=TEvent(data)
		Local codearea:TCodeArea
		
		If event
			codearea=TCodeArea(context)
			If codearea
				If Not codearea.ProcessEvent(event) Return data'Null
			EndIf
		EndIf
		Return data
	EndFunction
	
	'---------------------------------------------------------------------------------------------
	'Creation
	'---------------------------------------------------------------------------------------------	
	Function Create:TCodeArea(x:Int,y:Int,width:Int,height:Int,group:TGadget,style:Int=CODEAREA_DEFAULT)
		Local codearea:TCodeArea=New TCodeArea
		Local textareastyle:Int
		
		If Not dummywindow
			dummywindow=CreateWindow("",0,0,400,300,Null,WINDOW_HIDDEN)
			dummytextarea=CreateTextArea(0,0,300,200,dummywindow)
		EndIf
		codearea.style=style
		If (CODEAREA_READONLY & style) textareastyle=TEXTAREA_READONLY
		codearea.textarea=CreateTextArea(x,y,width,height,group,textareastyle)
		SetGadgetFilter codearea.textarea,Callback,codearea
		codearea.SetProxy(codearea.textarea)
		AddHook EmitEventHook,EventHook,codearea
		Return codearea
	EndFunction
	
EndType

'---------------------------------------------------------------------------------------------
'Procedural functions
'---------------------------------------------------------------------------------------------
Function CreateCodeArea:TCodeArea(x:Int,y:Int,width:Int,height:Int,group:TGadget,style:Int=CODEAREA_DEFAULT)
	Return TCodeArea.Create(x,y,width,height,group,style)
EndFunction

Function AddCodeAreaKeyword(codearea:TCodeArea,word:String)
	codearea.AddKeyword(word)
EndFunction

Function SetCodeAreaFormat(codearea:TCodeArea,element:Int,r:Int,g:Int,b:Int,style:Int=0)
	codearea.SetFormat(element,r,g,b,style)
EndFunction

Function CodeAreaClearUndos(codearea:TCodeArea)
	codearea.ClearUndos()
EndFunction

Function CodeAreaCanUndo:Int(codearea:TCodeArea)
	Return codearea.CanUndo()
EndFunction

Function CodeAreaCanRedo:Int(codearea:TCodeArea)
	Return codearea.CanRedo()	
EndFunction

Function CodeAreaUndo(codearea:TCodeArea)
	codearea.Undo()
EndFunction

Function CodeAreaRedo(codearea:TCodeArea)
	codearea.Redo()	
EndFunction

Function SetCodeAreaLanguage(codearea:TCodeArea,language:Int)
	codearea.SetLanguage language
EndFunction


'---------------------------------------------------------------------------------------------
'Example
'---------------------------------------------------------------------------------------------


'Create window
Local window:TGadget=CreateWindow("CodeArea Example",0,0,1024,768,Null,WINDOW_DEFAULT|WINDOW_CENTER)

'Create menu
Local root:TGadget=CreateMenu("Edit",0,WindowMenu(window))
Local menu:TGadget[6]
menu[0]=CreateMenu("Undo",0,root,KEY_Z,MODIFIER_COMMAND)
menu[1]=CreateMenu("Redo",1,root,KEY_Z,MODIFIER_COMMAND|MODIFIER_OPTION)
CreateMenu("",0,root)
DisableMenu menu[0]
DisableMenu menu[1]
menu[2]=CreateMenu("Clear Undos",2,root)
CreateMenu("",0,root)
menu[3]=CreateMenu("Cut",3,root,KEY_X,MODIFIER_COMMAND)
menu[4]=CreateMenu("Copy",4,root,KEY_C,MODIFIER_COMMAND)
menu[5]=CreateMenu("Paste",5,root,KEY_V,MODIFIER_COMMAND)
DisableMenu menu[3]
DisableMenu menu[4]
UpdateWindowMenu window

'Create CodeArea gadget
Local codearea:TCodeArea=CreateCodearea(0,0,ClientWidth(window),ClientHeight(window),window)
SetGadgetLayout codearea,1,1,1,1

'Add some keywords
AddCodeAreaKeyword codearea,"Allegience"
AddCodeAreaKeyword codearea,"Flag"
AddCodeAreaKeyword codearea,"Justice"

'Adjust the syntax highlighting
SetCodeAreaFormat codearea,CODEAREA_KEYWORD,0,0,255,FONT_BOLD
SetCodeAreaFormat codearea,CODEAREA_COMMENT,0,128,0,FONT_ITALIC

'Set the language and add some text

'C/C++
SetCodeAreaLanguage codearea,CODEAREA_C
SetGadgetText codearea,"#define whatever 3~n~nI ~qpledge~q allegience to the flag of the United States of /*America~nAnd to the republic for which it stands~n~qOne nation~q, under God,*/ indivisible~nWith liberty and justice //for all.~n"

'Lua
'SetCodeAreaLanguage codearea,CODEAREA_LUA
'SetGadgetText codearea,"I ~qpledge~q allegience to 'the' flag of the United States of --[[America~nAnd to the republic for which it stands~n~qOne nation~q, under God,]]-- indivisible~nWith liberty and justice --for all.~n"

'BlitzMax
'SetCodeAreaLanguage codearea,CODEAREA_BMX
'SetGadgetText codearea,"?debug~nPrint ~qHello~q~n?~n~nI ~qpledge~q allegience To the flag of the United States of America~nRem~nAnd To the republic For which it stands~n~qOne nation~q, under God, indivisible~nEndRem~nWith liberty and justice 'for all.~n"


'Set the font (we can do this any time)
SetGadgetFont codearea,LoadGuiFont("Courier New",10)

'Set the gadget text and background colors (we can do this at any time)
SetGadgetColor codearea,240,240,240,True
SetGadgetColor codearea,0,0,0,False



While WaitEvent()
	Select EventID()
		
		Case EVENT_GADGETSELECT
			If TextAreaSelLen(codearea)
				EnableMenu menu[3]
				EnableMenu menu[4]
			Else
				DisableMenu menu[3]
				DisableMenu menu[4]
			EndIf
			UpdateWindowMenu window	
			
		Case EVENT_GADGETACTION
			If TextAreaSelLen(codearea)
				EnableMenu menu[3]
				EnableMenu menu[4]
			Else
				DisableMenu menu[3]
				DisableMenu menu[4]
			EndIf
			If codearea.canundo() EnableMenu menu[0] Else DisableMenu menu[0]
			If codearea.canredo() EnableMenu menu[1] Else DisableMenu menu[1]
			UpdateWindowMenu window			
			
		Case EVENT_MENUACTION
			Select EventData()
			Case 1 codearea.redo()
			Case 0 codearea.undo()
			Case 2 codearea.ClearUndos()
			Case 3 GadgetCut(codearea)
			Case 4 GadgetCopy(codearea)
			Case 5 GadgetPaste(codearea)
			EndSelect
			If codearea.canundo() EnableMenu menu[0] Else DisableMenu menu[0]
			If codearea.canredo() EnableMenu menu[1] Else DisableMenu menu[1]
			UpdateWindowMenu window
			
		Case EVENT_WINDOWCLOSE
			End
			
	EndSelect
	'Print CurrentEvent.ToString()
Wend

Comments

JoshK2011
I'm going to post new versions below in case I mess anything up. This version will handle End, Home, PageUp, PageDown, Delete, and Insert keys:



Oddball2011
This looks very interesting. I don't need it for anything at the moment, but I'll bookmark it just in case it's useful at a later date. Thanks for sharing.


impixi2011
Impressive!


degac2011
Thanks for sharing, very good!


JoshK2011
The multi-line comments still don't work right. This is really hard.


JoshK2011
I tried a different approach for handling the multi-line comments. When a line changes its begin or end mult-line comment state, it crawls in one direction or the other looking for its corresponding begin or end statement. It's super fast and I haven't been able to produce any errors yet:



JoshK2011
This code has a lot of errors on OSX.


JoshK2011
This version is modified to work on OSX and it handles multiline comments correctly, even when doing lots of cut and paste operations with multiple lines of text:



JoshK2011
More small fixes:



Code Archives Forum