Browse Source

Squashed 'src/ted2go/' changes from 43463bbc..3aee91f1

3aee91f1 Improvements. (#125)
ec4cf5ae Added some monkeydocs templates (#118)

git-subtree-dir: src/ted2go
git-subtree-split: 3aee91f123e8b21d01fcd16626eea32bef4b9c80
Mark Sibly 7 years ago
parent
commit
e7af27595e

+ 55 - 18
MainWindow.monkey2

@@ -81,6 +81,8 @@ Class MainWindowInstance Extends Window
 			SaveState()
 		End
 		
+		_recentViewedFiles=New RecentFiles( _docsManager )
+		
 		'Build tab
 		
 		_buildConsole=New ConsoleExt
@@ -232,7 +234,7 @@ Class MainWindowInstance Extends Window
 		_editActions=New EditActions( _docsManager )
 		_findActions=New FindActions( _docsManager,_projectView,_findConsole )
 		_helpActions=New HelpActions
-		_viewActions=New ViewActions( _docsManager )
+		_gotoActions=New GotoActions( _docsManager )
 		_tabActions=New TabActions( _tabsWrap.AllDocks )
 		
 		_tabMenu=New Menu
@@ -321,8 +323,8 @@ Class MainWindowInstance Extends Window
 		_editMenu.AddSubMenu( subText )
 		' Edit -- Comment
 		Local subComment:=New MenuExt( "Comment" )
-		subComment.AddAction( _viewActions.comment )
-		subComment.AddAction( _viewActions.uncomment )
+		subComment.AddAction( _editActions.comment )
+		subComment.AddAction( _editActions.uncomment )
 		_editMenu.AddSubMenu( subComment )
 		' Edit -- Convert case
 		Local subCase:=New MenuExt( "Convert case" )
@@ -348,16 +350,44 @@ Class MainWindowInstance Extends Window
 		'Goto menu
 		'
 		_gotoMenu=New MenuExt( "Goto" )
-		_gotoMenu.AddAction( _viewActions.gotoLine )
-		_gotoMenu.AddAction( _viewActions.gotoDeclaration )
+		_gotoMenu.AddAction( _gotoActions.gotoLine )
+		_gotoMenu.AddAction( _gotoActions.gotoDeclaration )
 		_gotoMenu.AddSeparator()
-		_gotoMenu.AddAction( _viewActions.goBack )
-		_gotoMenu.AddAction( _viewActions.goForward )
+		_gotoMenu.AddAction( _gotoActions.goBack )
+		_gotoMenu.AddAction( _gotoActions.goForward )
 		
 		'View menu
 		'
 		_viewMenu=New MenuExt( "View" )
-		TabActions.CreateMenu( _viewMenu )
+		Local m:=New MenuExt( "Docks" )
+		TabActions.CreateMenu( m )
+		_viewMenu.AddSubMenu( m )
+		_viewMenu.AddSeparator()
+		m=New MenuExt( "Folding" )
+		Local foldActions:=New FoldingActions
+		m.AddAction( foldActions.foldCurrent )
+		m.AddAction( foldActions.unfoldCurrent )
+		m.AddSeparator()
+		m.AddAction( foldActions.foldScope )
+		m.AddAction( foldActions.unfoldScope )
+		m.AddSeparator()
+		m.AddAction( foldActions.foldAll )
+		m.AddAction( foldActions.unfoldAll )
+		_viewMenu.AddSubMenu( m )
+		_viewMenu.AddSeparator()
+		Local showRecent:=New Action( "Recently viewed files...")
+		showRecent.Triggered=Lambda()
+			Local path:=_recentViewedFiles.ShowDialog()
+			If path Then OpenDocument( path )
+		End
+		showRecent.HotKey=Key.E
+		#If __TARGET__="macos"
+		showRecent.HotKeyModifiers=Modifier.Menu
+		#Else
+		showRecent.HotKeyModifiers=Modifier.Control
+		#Endif
+		_viewMenu.AddAction( showRecent )
+		
 		'Build menu
 		'
 		_forceStop=New Action( "Force Stop" )
@@ -891,15 +921,15 @@ Class MainWindowInstance Extends Window
 		Local cutTitle:=GetActionTextWithShortcut( _editActions.cut )
 		Local copyTitle:=GetActionTextWithShortcut( _editActions.copy )
 		Local pasteTitle:=GetActionTextWithShortcut( _editActions.paste )
-		Local goBackTitle:=GetActionTextWithShortcut( _viewActions.goBack )
-		Local goForwTitle:=GetActionTextWithShortcut( _viewActions.goForward )
+		Local goBackTitle:=GetActionTextWithShortcut( _gotoActions.goBack )
+		Local goForwTitle:=GetActionTextWithShortcut( _gotoActions.goForward )
 		
 		_toolBar=New ToolBarExt
 		_toolBar.Style=GetStyle( "MainToolBar" )
 		_toolBar.MaxSize=New Vec2i( 10000,40 )
 		
-		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/back.png" ),_viewActions.goBack.Triggered,goBackTitle )
-		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/forward.png" ),_viewActions.goForward.Triggered,goForwTitle )
+		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/back.png" ),_gotoActions.goBack.Triggered,goBackTitle )
+		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/forward.png" ),_gotoActions.goForward.Triggered,goForwTitle )
 		_toolBar.AddSeparator()
 		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/new_file.png" ),_fileActions.new_.Triggered,newTitle )
 		_toolBar.AddIconicButton( ThemeImages.Get( "toolbar/open_file.png" ),_fileActions.open.Triggered,openTitle )
@@ -1099,14 +1129,14 @@ Class MainWindowInstance Extends Window
 		
 		If Not _editorMenu
 			_editorMenu=New MenuExt
-			_editorMenu.AddAction( _viewActions.gotoDeclaration )
+			_editorMenu.AddAction( _gotoActions.gotoDeclaration )
 			_editorMenu.AddSeparator()
 			_editorMenu.AddAction( _editActions.cut )
 			_editorMenu.AddAction( _editActions.copy )
 			_editorMenu.AddAction( _editActions.paste )
 			_editorMenu.AddSeparator()
-			_editorMenu.AddAction( _viewActions.comment )
-			_editorMenu.AddAction( _viewActions.uncomment )
+			_editorMenu.AddAction( _editActions.comment )
+			_editorMenu.AddAction( _editActions.uncomment )
 		Endif
 		
 		_editorMenu.Open()
@@ -1267,6 +1297,11 @@ Class MainWindowInstance Extends Window
 		UpdateWindow( True )
 	End
 	
+	Method OpenProject( path:String )
+		
+		_projectView.OpenProject( path )
+	End
+	
 	Method GetActionFind:Action()
 	
 		Return _findActions.find
@@ -1274,12 +1309,12 @@ Class MainWindowInstance Extends Window
 	
 	Method GetActionComment:Action()
 	
-		Return _viewActions.comment
+		Return _editActions.comment
 	End
 	
 	Method GetActionUncomment:Action()
 	
-		Return _viewActions.uncomment
+		Return _editActions.uncomment
 	End
 	
 
@@ -1678,7 +1713,7 @@ Class MainWindowInstance Extends Window
 	Field _findActions:FindActions
 	Field _buildActions:BuildActions
 	Field _helpActions:HelpActions
-	Field _viewActions:ViewActions
+	Field _gotoActions:GotoActions
 	Field _tabActions:TabActions
 	
 	Field _buildConsole:ConsoleExt
@@ -1741,6 +1776,8 @@ Class MainWindowInstance Extends Window
 	Field _fullscreenHelper:= New FullscreenHelper
 	Field _storedSize:Recti,_storedMaximized:Bool
 	
+	Field _recentViewedFiles:RecentFiles
+	
 	Method ToJson:JsonValue( rect:Recti )
 		Return New JsonArray( New JsonValue[]( New JsonNumber( rect.min.x ),New JsonNumber( rect.min.y ),New JsonNumber( rect.max.x ),New JsonNumber( rect.max.y ) ) )
 	End

+ 3 - 0
Prefs.monkey2

@@ -36,6 +36,7 @@ Class PrefsInstance
 	Field EditorUseSpacesAsTabs:=False
 	Field EditorTabSize:=4
 	Field EditorRemoveLinesTrailing:=False
+	Field EditorLineSpacing:=1.0
 	'
 	Field SourceSortByType:=True
 	Field SourceShowInherited:=False
@@ -97,6 +98,7 @@ Class PrefsInstance
 			EditorUseSpacesAsTabs=Json_GetBool( j2,"useSpacesAsTabs",EditorUseSpacesAsTabs )
 			EditorTabSize=Json_GetInt( j2,"tabSize",EditorTabSize )
 			EditorRemoveLinesTrailing=Json_GetBool( j2,"removeLinesTrailing",EditorRemoveLinesTrailing )
+			EditorLineSpacing=Json_GetFloat( j2,"lineSpacing",EditorLineSpacing )
 			
 		Endif
 		
@@ -152,6 +154,7 @@ Class PrefsInstance
 		j["useSpacesAsTabs"]=New JsonBool( EditorUseSpacesAsTabs )
 		j["tabSize"]=New JsonNumber( EditorTabSize )
 		j["removeLinesTrailing"]=New JsonBool( EditorRemoveLinesTrailing )
+		j["lineSpacing"]=New JsonNumber( EditorLineSpacing )
 		
 		j=New JsonObject
 		json["source"]=j

+ 9 - 4
Ted2.monkey2

@@ -30,9 +30,10 @@
 #Import "action/BuildActions"
 #Import "action/HelpActions"
 #Import "action/FindActions"
-#Import "action/ViewActions"
+#Import "action/GotoActions"
 #Import "action/WindowActions"
 #Import "action/TabActions"
+#Import "action/FoldingActions"
 
 #Import "dialog/FindDialog"
 #Import "dialog/PrefsDialog"
@@ -42,6 +43,7 @@
 #Import "dialog/FindInFilesDialog"
 #Import "dialog/UpdateModulesDialog"
 #Import "dialog/GenerateClassDialog"
+#Import "dialog/RecentFilesDialog"
 
 #Import "document/DocumentManager"
 #Import "document/Ted2Document"
@@ -83,7 +85,6 @@
 #Import "utils/Utils"
 #Import "utils/TextUtils"
 
-#Import "view/IRCView"
 #Import "view/CodeMapView"
 #Import "view/CodeTextView"
 #Import "view/ConsoleViewExt"
@@ -117,6 +118,8 @@
 #Import "view/DockingViewExt"
 #Import "view/DraggableViewListener"
 #Import "view/Undock"
+#Import "view/TextViewExt"
+
 
 #Import "Tree"
 #Import "Tuple"
@@ -138,7 +141,7 @@ Using sdl2..
 
 Const MONKEY2_DOMAIN:="http://monkeycoder.co.nz"
 
-Global AppTitle:="Ted2Go v2.9"
+Global AppTitle:="Ted2Go v2.10"
 
 
 Function Main()
@@ -167,7 +170,7 @@ Function Main()
 	
 	'initial theme
 	'
-	If Not jobj.Contains( "theme" ) jobj["theme"]=New JsonString( "theme-warm" )
+	If Not jobj.Contains( "theme" ) jobj["theme"]=New JsonString( "theme-hollow" )
 
 	If Not jobj.Contains( "themeScale" ) jobj["themeScale"]=New JsonNumber( 1 )
 	
@@ -205,6 +208,8 @@ Function Main()
 			arg=arg.Replace( "\","/" )
 			If GetFileType( arg ) = FileType.File
 				MainWindow.OpenDocument( arg,True )
+			Elseif GetFileType( arg ) = FileType.Directory
+				MainWindow.OpenProject( arg )
 			Endif
 		Next
 	End

+ 37 - 5
action/EditActions.monkey2

@@ -20,6 +20,9 @@ Class EditActions
 	Field textLowercase:Action
 	Field textUppercase:Action
 	Field textSwapCase:Action
+	'
+	Field comment:Action
+	Field uncomment:Action
 	
 	Method New( docs:DocumentManager )
 	
@@ -67,13 +70,8 @@ Class EditActions
 		
 		textDeleteLine=New Action( "Delete line" )
 		textDeleteLine.Triggered=OnDeleteLine
-		#If __TARGET__="macos"
 		textDeleteLine.HotKey=Key.K
 		textDeleteLine.HotKeyModifiers=Modifier.Control|Modifier.Shift
-		#Else
-		textDeleteLine.HotKey=Key.E
-		textDeleteLine.HotKeyModifiers=Modifier.Control
-		#Endif
 		
 		textDeleteWordForward=New Action( "Delete word forward" )
 		textDeleteWordForward.Triggered=OnDeleteWordForward
@@ -117,6 +115,24 @@ Class EditActions
 		textSwapCase=New Action( "Swap case" )
 		textSwapCase.Triggered=OnSwapCase
 		
+		comment=New Action( "Comment block" )
+		comment.Triggered=OnComment
+		#If __TARGET__="macos"
+		comment.HotKey=Key.Backslash
+		#Else
+		comment.HotKey=Key.Apostrophe
+		#Endif
+		comment.HotKeyModifiers=Modifier.Menu
+		
+		uncomment=New Action( "Uncomment block" )
+		uncomment.Triggered=OnUncomment
+		#If __TARGET__="macos"
+		uncomment.HotKey=Key.Backslash
+		#Else
+		uncomment.HotKey=Key.Apostrophe
+		#Endif
+		uncomment.HotKeyModifiers=Modifier.Menu|Modifier.Shift
+		
 	End
 	
 	Method Update()
@@ -240,4 +256,20 @@ Class EditActions
 		Endif
 	End
 	
+	Method OnComment()
+	
+		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
+		If Not doc Return
+	
+		doc.Comment()
+	End
+	
+	Method OnUncomment()
+	
+		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
+		If Not doc Return
+	
+		doc.Uncomment()
+	End
+	
 End

+ 6 - 1
action/FindActions.monkey2

@@ -52,8 +52,13 @@ Class FindActions
 		
 		findInFiles=New Action( "Find in files..." )
 		findInFiles.Triggered=Lambda()
-			Local proj:=projView.FindProjectByFile( docs.CurrentDocument.Path )
+			
+			Local path:=docs.CurrentDocument?.Path
+			If Not path Then path=projView.SelectedItem?.Path
+			
+			Local proj:=projView.FindProjectByFile( path )
 			OnFindInFiles( "",proj )
+			
 		End
 		findInFiles.HotKey=Key.F
 		findInFiles.HotKeyModifiers=Modifier.Menu|Modifier.Shift

+ 122 - 0
action/FoldingActions.monkey2

@@ -0,0 +1,122 @@
+
+Namespace ted2go
+
+
+Class FoldingActions
+	
+	Field foldCurrent:Action
+	Field foldScope:Action
+	Field foldAll:Action
+	Field unfoldCurrent:Action
+	Field unfoldScope:Action
+	Field unfoldAll:Action
+	
+	Method New()
+		
+		foldCurrent=New Action( "Fold current" )
+		foldCurrent.Triggered=OnFoldCurrent
+		foldCurrent.HotKey=Key.Minus
+		foldCurrent.HotKeyModifiers=Modifier.Alt
+		
+		foldScope=New Action( "Fold current & parents" )
+		foldScope.Triggered=OnFoldScope
+		foldScope.HotKey=Key.Minus
+		foldScope.HotKeyModifiers=Modifier.Alt|Modifier.Shift
+		
+		foldAll=New Action( "Fold all" )
+		foldAll.Triggered=OnFoldAll
+		foldAll.HotKey=Key.Minus
+		foldAll.HotKeyModifiers=Modifier.Alt|Modifier.Shift|Modifier.Control
+		
+		unfoldCurrent=New Action( "Unfold current" )
+		unfoldCurrent.Triggered=OnUnfoldCurrent
+		unfoldCurrent.HotKey=Key.Equals
+		unfoldCurrent.HotKeyModifiers=Modifier.Alt
+		
+		unfoldScope=New Action( "Unfold current & children" )
+		unfoldScope.Triggered=OnUnfoldScope
+		unfoldScope.HotKey=Key.Equals
+		unfoldScope.HotKeyModifiers=Modifier.Alt|Modifier.Shift
+		
+		unfoldAll=New Action( "Unfold all" )
+		unfoldAll.Triggered=OnUnfoldAll
+		unfoldAll.HotKey=Key.Equals
+		unfoldAll.HotKeyModifiers=Modifier.Alt|Modifier.Shift|Modifier.Control
+		
+	End
+	
+	
+	Private
+	
+	Property CurrentCodeDocument:CodeTextView()
+		
+		Return Cast<CodeTextView>( App.KeyView )
+	End
+	
+	Method OnFoldCurrent()
+		
+		Local code:=CurrentCodeDocument
+		If code
+			code.FoldBlock( code.LineNumAtCursor,True,True )
+		Endif
+	End
+	
+	Method OnFoldScope()
+		
+		Local code:=CurrentCodeDocument
+		If code
+			Local f:=code.FindNearestFolding( code.LineNumAtCursor )
+			If Not f Return
+			While f And f.parent ' find root folding
+				f=f.parent
+			Wend
+			Local all:=New Stack<CodeTextView.Folding>
+			For Local i:=f.startLine Until f.endLine
+				Local f2:=code.GetFolding( i )
+				If f2 Then all.Add( f2 )
+			Next
+			For Local i:=all.Length-1 To 0 Step -1
+				code.FoldBlock( all[i].startLine,(i=0) )
+			Next
+		Endif
+	End
+	
+	Method OnFoldAll()
+		
+		CurrentCodeDocument?.FoldAll()
+	End
+	
+	Method OnUnfoldCurrent()
+		
+		Local code:=CurrentCodeDocument
+		If code
+			code.UnfoldBlock( code.LineNumAtCursor,True,True )
+		Endif
+	End
+	
+	Method OnUnfoldScope()
+		
+		Local code:=CurrentCodeDocument
+		If code
+			Local f:=code.FindNearestFolding( code.LineNumAtCursor )
+			If Not f Return
+			While f And f.parent ' find root folding
+				f=f.parent
+			Wend
+			Local all:=New Stack<CodeTextView.Folding>
+			For Local i:=f.startLine Until f.endLine
+				Local f2:=code.GetFolding( i )
+				If f2 Then all.Add( f2 )
+			Next
+			For Local i:=0 Until all.Length
+				code.UnfoldBlock( all[i].startLine,(i=all.Length-1) )
+			Next
+		Endif
+	End
+	
+	Method OnUnfoldAll()
+		
+		CurrentCodeDocument?.UnfoldAll()
+	End
+	
+End

+ 1 - 37
action/ViewActions.monkey2 → action/GotoActions.monkey2

@@ -2,12 +2,10 @@
 Namespace ted2go
 
 
-Class ViewActions
+Class GotoActions
 	
 	Field goBack:Action
 	Field goForward:Action
-	Field comment:Action
-	Field uncomment:Action
 	Field gotoLine:Action
 	Field gotoDeclaration:Action
 	
@@ -25,24 +23,6 @@ Class ViewActions
 		goForward.HotKey=Key.Right
 		goForward.HotKeyModifiers=Modifier.Alt|Modifier.Menu
 		
-		comment=New Action( "Comment block" )
-		comment.Triggered=OnComment
-#If __TARGET__="macos"
-		comment.HotKey=Key.Backslash
-#Else
-		comment.HotKey=Key.Apostrophe
-#Endif
-		comment.HotKeyModifiers=Modifier.Menu
-		
-		uncomment=New Action( "Uncomment block" )
-		uncomment.Triggered=OnUncomment
-#If __TARGET__="macos"
-		uncomment.HotKey=Key.Backslash
-#Else
-		uncomment.HotKey=Key.Apostrophe
-#Endif
-		uncomment.HotKeyModifiers=Modifier.Menu|Modifier.Shift
-		
 		gotoLine=New Action( "Goto line" )
 		gotoLine.Triggered=OnGotoLine
 		gotoLine.HotKey=Key.G
@@ -74,22 +54,6 @@ Class ViewActions
 		doc.GoForward()
 	End
 	
-	Method OnComment()
-	
-		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
-		If Not doc Return
-	
-		doc.Comment()
-	End
-	
-	Method OnUncomment()
-	
-		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
-		If Not doc Return
-	
-		doc.Uncomment()
-	End
-	
 	Method OnGotoLine()
 	
 		MainWindow.GotoLine()

+ 25 - 24
action/TabActions.monkey2

@@ -12,42 +12,42 @@ Class TabActions
 	
 	Function SwitchView( tabName:String )
 		
-			For Local i:=Eachin tabs
-				Local _docks:=i.Tabs
-				For Local _tabb:=Eachin _docks	
-					If _tabb.Text=tabName Then
-						If _tabb.Visible
-							_tabb.Visible=False
-							_tabb.View.Visible=False
-							'make first tab as current
-							For Local firstCurrent:=Eachin _docks
-								If firstCurrent.Visible Then firstCurrent.CurrentHolder.MakeCurrent( firstCurrent.Text ); Exit
-							Next	
-						Else
-							If( _tabb.View )
-								_tabb.Visible=True
-								_tabb.View.Visible=True
-								_tabb.CurrentHolder.MakeCurrent( _tabb.Text )
-							End
+		For Local i:=Eachin tabs
+			Local _docks:=i.Tabs
+			For Local _tabb:=Eachin _docks
+				If _tabb.Text=tabName Then
+					If _tabb.Visible
+						_tabb.Visible=False
+						_tabb.View.Visible=False
+						'make first tab as current
+						For Local firstCurrent:=Eachin _docks
+							If firstCurrent.Visible Then firstCurrent.CurrentHolder.MakeCurrent( firstCurrent.Text ); Exit
+						Next
+					Else
+						If( _tabb.View )
+							_tabb.Visible=True
+							_tabb.View.Visible=True
+							_tabb.CurrentHolder.MakeCurrent( _tabb.Text )
 						End
-						_tabb.CurrentHolder.Visible=_tabb.CurrentHolder.VisibleTabs
 					End
-					
-				Next
+					_tabb.CurrentHolder.Visible=_tabb.CurrentHolder.VisibleTabs
+				End
+				
 			Next
+		Next
 	End
 	
 	Function CreateMenu( view:MenuExt )
 		
 		Local keynr:Int
-		Local tabNames:=New String[]( "Project","Debug","Source","Build","Output","Docs","Find","Chat" )	
+		Local tabNames:=New String[]( "Project","Debug","Source","Build","Output","Docs","Find","Chat" )
 		For Local a:=Eachin tabNames
 			Local key:=Cast<Key>( 49+keynr )
 			Local i:=view.AddAction( a )
 			i.HotKey=key
 			i.HotKeyModifiers=Modifier.Alt
 			i.Triggered=Lambda()
-				SwitchView( a )	
+				SwitchView( a )
 			End
 			keynr+=1
 		Next
@@ -55,7 +55,7 @@ Class TabActions
 		'reset all tabs
 		Local _reset:=view.AddAction( "Reset" )
 		_reset.Triggered=Lambda()
-			Reset()	
+			Reset()
 		End
 	End
 	
@@ -63,7 +63,7 @@ Class TabActions
 		
 		For Local i:=Eachin tabs
 			Local _docks:=i.Tabs
-			For Local _tabb:=Eachin _docks	
+			For Local _tabb:=Eachin _docks
 					If _tabb.View
 						_tabb.Visible=True
 						_tabb.View.Visible=True
@@ -75,4 +75,5 @@ Class TabActions
 		'Undock Reset
 		UndockWindow.RestoreUndock()
 	End
+	
 End

+ 5 - 1
assets/aboutTed2Go.html

@@ -71,6 +71,10 @@ Press F12 (or F2) to goto definition of ident under cursor.
 You can see parse errors in realtime. Point the mouse to see a hint.
 </li>
 <br>
+<li><b>Code folding</b><br>
+Fold members to have more crear editor area.
+</li>
+<br>
 <li><b>ToolBars</b><br>
 MainToolBar - to simplify access to actions: open/save/undo/redo/build.<br>
 EditorToolBar: find/comment/shift.
@@ -124,7 +128,7 @@ ToolBar icons were taken from <a href="https://icons8.com/icon/new-icons/win8">i
 
 <h3>Special thanks</h3>
 
-<p>Dominique MIS, Rajasekaran Senthil Kumaran, abakobo, Peter Rigby, Dmitry Fadeev, James Boyd, Sam Fisher, Matthew Bowen, Mark Mcvittie, Simon Armstrong, Rudy van Etten, Hezkore, Sal Gunduz, Peter Scheutz, Matthieu Chemin, David Maziarka, Leonardo Teixeira, Jesus Perez, Mark Sibly, Philipp Moeller, Lee Wade.</p>
+<p>Second Gear Games, Dominique MIS, Rajasekaran Senthil Kumaran, abakobo, Peter Rigby, Dmitry Fadeev, James Boyd, Sam Fisher, Matthew Bowen, Mark Mcvittie, Simon Armstrong, Rudy van Etten, Hezkore, Sal Gunduz, Peter Scheutz, Matthieu Chemin, David Maziarka, Leonardo Teixeira, Jesus Perez, Mark Sibly, Philipp Moeller, Lee Wade.</p>
 
 </body>
 

+ 4 - 1
assets/liveTemplates.json

@@ -1,6 +1,9 @@
 {
 	".monkey2":{
 		"do":"Repeat\n\t${Cursor}\nForever",
+		"doca":"#Rem monkeydoc ${cursor}\n\t...\n\t@param val descr\n\t\n    @example\n    \t...\n    @end\n#End\n",
+		"docd":"#Rem monkeydoc ${Cursor}\n\tdetails [[class name]]\n#End\n",
+		"docs":"#Rem monkeydoc ${Cursor}\n#End",
 		"dou":"Repeat\n\t\nUntil _${Cursor}",
 		"each":"For Local ${Cursor}:=Eachin _\n\t\nNext",
 		"fib":"New Fiber( Lambda()\n\t${Cursor}\nEnd )",
@@ -27,4 +30,4 @@
 		"whi":"While ${Cursor}\n\t\nWend",
 		"whn":"While ${Cursor} <> _\n\t\nWend"
 	}
-}
+}

+ 0 - 1
assets/themes/theme-hollow.json

@@ -311,7 +311,6 @@
 		"MenuButton":{
 			"extends":"Label",
 			"padding":[8,3],
-			"textColor":"text-default",
 			"states":{
 				"hover":{
 					"textColor":"text-highlight",

+ 11 - 1
dialog/PrefsDialog.monkey2

@@ -86,6 +86,7 @@ Class PrefsDialog Extends DialogExt
 	Field _editorUseSpacesAsTabs:CheckButton
 	Field _editorTabSize:TextFieldExt
 	Field _editorRemoveLinesTrailing:CheckButton
+	Field _editorLineSpacing:TextFieldExt
 	
 	Field _mainToolBarVisible:CheckButton
 	Field _mainProjectIcons:CheckButton
@@ -140,6 +141,7 @@ Class PrefsDialog Extends DialogExt
 		If Not size Then size="4" 'default
 		Prefs.EditorTabSize=Clamp( Int(size),1,16 )
 		Prefs.EditorRemoveLinesTrailing=_editorRemoveLinesTrailing.Checked
+		Prefs.EditorLineSpacing=Clamp( Float(_editorLineSpacing.Text.Trim()),0.5,2.5 )
 		
 		Prefs.MainToolBarVisible=_mainToolBarVisible.Checked
 		Prefs.MainProjectIcons=_mainProjectIcons.Checked
@@ -252,6 +254,8 @@ Class PrefsDialog Extends DialogExt
 		_editorRemoveLinesTrailing=New CheckButton( "Remove whitespaced trailings on saving" )
 		_editorRemoveLinesTrailing.Checked=Prefs.EditorRemoveLinesTrailing
 		
+		_editorLineSpacing=New TextFieldExt( ""+Prefs.EditorLineSpacing )
+		
 		Local path:=Prefs.EditorFontPath
 		If Not path Then path=_defaultFont
 		_editorFontPath=New TextFieldExt( "" )
@@ -299,6 +303,12 @@ Class PrefsDialog Extends DialogExt
 		tabs.AddView( _editorTabSize,"left" )
 		tabs.AddView( _editorUseSpacesAsTabs,"left" )
 		
+		Local lineSpacing:=New DockingView
+		lineSpacing.AddView( New Label( "Line spacing interval:" ),"left" )
+		_editorLineSpacing.MaxSize=New Vec2i( 100,100 )
+		lineSpacing.AddView( _editorLineSpacing,"left" )
+		lineSpacing.AddView( New Label( "  0.5...2.5" ),"left" )
+		
 		Local docker:=New DockingView
 		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _editorToolBarVisible,"top" )
@@ -313,6 +323,7 @@ Class PrefsDialog Extends DialogExt
 		docker.AddView( _editorShowParamsHint,"top" )
 		docker.AddView( _editorRemoveLinesTrailing,"top" )
 		docker.AddView( tabs,"top" )
+		docker.AddView( lineSpacing,"top" )
 		docker.AddView( New Label( " " ),"top" )
 		
 		
@@ -501,7 +512,6 @@ Class PrefsDialog Extends DialogExt
 			Return
 		Endif
 		
-		DebugStop()
 		LiveTemplates[TemplateSelLang].Remove( TemplateSelName )
 		_treeView.RemoveNode( _treeView.Selected )
 		_codeView.Text=""

+ 230 - 0
dialog/RecentFilesDialog.monkey2

@@ -0,0 +1,230 @@
+
+Namespace ted2go
+
+
+Class RecentFiles
+	
+	Method New( docs:DocumentManager )
+		
+		docs.CurrentDocumentChanged+=Lambda()
+			
+			Local path:=docs.CurrentDocument?.Path
+			If path Then Add( path )
+		End
+	End
+	
+	Method ShowDialog:String()
+		
+		Local dialog:=New RecentFilesDialog( Files )
+		Local ok:=dialog.ShowModal()
+		
+		Return ok ? dialog.SelPath Else ""
+	End
+	
+	Private
+	
+	Field _files:=New StringStack
+	
+	Method Add( path:String )
+	
+		_files.Remove( path )
+		_files.Insert( 0,path )
+	End
+	
+	Property Files:String[]()
+	
+		Return _files.ToArray()
+	End
+	
+End
+
+
+Class RecentFilesDialog Extends DialogExt
+
+	Method New( files:String[] )
+		
+		_labelFilter=New Label
+		'_labelFilter.MinSize=New Vec2i( 50,24 )
+		
+		' convert texts into items
+		For Local i:=Eachin files
+			_listItems.Add( New FileListViewItem( GetShortPath( i ),i ) )
+		Next
+		
+		_listView=New AutocompleteListView( 20,100 )
+		_listView.Layout="fill-x"
+		_listView.MinSize=New Vec2i( 300,400 )
+		SelectRelevantItem()
+		_listView.OnItemChoosenDblClick+=Lambda()
+			HideWithResult( True )
+		End
+		
+		_docker=New DockingView
+		_docker.AddView( _labelFilter,"top" )
+		_docker.AddView( _listView,"top" )
+		_docker.AddView( New Label( " " ),"top" )
+		
+		Title="Recently viewed files"
+		
+		MinSize=New Vec2i( 300,400 )
+		
+		ContentView=_docker
+		
+		Local close:=AddAction( "Close" )
+		SetKeyAction( Key.Escape,close )
+		close.Triggered=Lambda()
+			HideWithResult( False )
+		End
+		
+		Local ok:=AddAction( "Open" )
+		SetKeyAction( Key.Enter,ok )
+		ok.Triggered=Lambda()
+			HideWithResult( True )
+		End
+		
+		Activated+=Lambda()
+			_listView.MakeKeyView()
+			App.KeyEventFilter+=OnKeyFilter
+		End
+		
+		Deactivated+=Lambda()
+			MainWindow.UpdateKeyView()
+			App.KeyEventFilter-=OnKeyFilter
+		End
+		
+	End
+	
+	Property SelPath:String()
+		
+		Return Cast<FileListViewItem>( _listView.CurrentItem )?.Path
+	End
+	
+	Private
+	
+	Field _listView:AutocompleteListView
+	Field _labelFilter:Label
+	Field _filter:String
+	Field _docker:DockingView
+	Field _listItems:=New Stack<ListViewItem>
+	
+	Method OnFilterChanged()
+		
+		_labelFilter.Text=_filter
+		_listView.word=_filter
+		SelectRelevantItem()
+		RequestRender()
+	End
+	
+	Method OnKeyFilter( event:KeyEvent )
+		
+		Select event.Type
+			
+			Case EventType.KeyDown,EventType.KeyRepeat
+				
+				Local key:=event.Key
+				Select key
+				
+				Case Key.Escape
+					Hide()
+					event.Eat()
+				
+				Case Key.Up
+					_listView.SelectPrev()
+					event.Eat()
+				
+				Case Key.Down
+					_listView.SelectNext()
+					event.Eat()
+				
+				Case Key.PageUp
+					_listView.PageUp()
+					event.Eat()
+				
+				Case Key.PageDown
+					_listView.PageDown()
+					event.Eat()
+				
+				Case Key.Enter,Key.KeypadEnter
+					'OnItemChoosen( curItem,key )
+					
+				Case Key.Backspace
+					If _filter
+						_filter=_filter.Slice( 0,_filter.Length-1 )
+						OnFilterChanged()
+					Endif
+			
+				End
+				
+			Case EventType.KeyChar
+				_filter+=event.Text
+				OnFilterChanged()
+			
+		End
+		
+	End
+	
+	Method SelectRelevantItem()
+		
+		If _listItems.Empty Return
+		
+		_listView.SetItems( _listItems )
+		
+		Local found:=0
+		If _filter
+			Local forDel:=New Stack<ListViewItem>
+			_listView.Sort( Lambda:Int( lhs:ListViewItem,rhs:ListViewItem )
+				
+				Local lp:=CodeItemsSorter.GetIdentPower( lhs.Text,_filter,False )
+				Local rp:=CodeItemsSorter.GetIdentPower( rhs.Text,_filter,False )
+				
+				If lp=0 Then forDel.Add( lhs )
+				If rp=0 Then forDel.Add( rhs )
+				
+				Local r:=(rp<=>lp)
+				If r=0 Return CodeItemsSorter.GetIdentLength( lhs )<=>CodeItemsSorter.GetIdentLength( rhs ) 'brings up shorter idents
+				
+				Return r
+			End )
+			' remove 'bad' variants
+			For Local del:=Eachin forDel
+				_listView.RemoveItem( del )
+			Next
+		Endif
+		
+		_listView.SelectByIndex( found )
+	End
+	
+	Function GetShortPath:String( path:String )
+		
+		' TODO - show parent folder?
+'		path=path.Replace( "/","\" )
+'		Local i:=path.FindLast( "\" )
+'		If i<>-1
+'			Local s:=path.Slice( 0,i-1 )
+'			Local i2:=s.FindLast( "\" )
+'			If i2<>-1 Then i=i2
+'			
+'			Return path.Slice( i+1,path.Length ).Replace( "\"," \ " )
+'		Endif
+		
+		Return StripDir( path )
+	End
+End
+
+Class FileListViewItem Extends ListViewItem
+	
+	Method New( text:String,path:String )
+		
+		Super.New( text )
+		_path=path
+	End
+	
+	Property Path:String()
+		Return _path
+	End
+	
+	Private
+	
+	Field _path:String
+	
+End

+ 108 - 42
document/CodeDocument.monkey2

@@ -58,6 +58,11 @@ Class CodeDocumentView Extends Ted2CodeTextView
 					
 					text=_doc.PrepareForInsert( ident,text,Not bySpace,LineTextAtCursor,PosInLineAtCursor,item )
 					Local i1:=Cursor-AutoComplete.LastIdentPart.Length
+					
+					If text.StartsWith( "#" ) And i1>0 And Text[i1-1]=Chars.GRID
+						i1-=1
+					Endif
+					
 					Local i2:=Cursor
 					If result.byTab
 						Local i:=Cursor
@@ -111,7 +116,12 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		Endif
 		If _codeMap Then _codeMap.Visible=visible
 		
+		'line spacing
+		LineSpacing=Prefs.EditorLineSpacing
+		
 		_doc.ArrangeElements()
+		
+		InvalidateStyle()
 	End
 	
 	
@@ -174,10 +184,10 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		_doc.HideHint_()
 		
-		Local alt:=(event.Modifiers & Modifier.Alt)
-		Local ctrl:=(event.Modifiers & Modifier.Control)
-		Local shift:=(event.Modifiers & Modifier.Shift)
-		Local menu:=(event.Modifiers & Modifier.Menu)
+		Local alt:=(event.Modifiers & Modifier.Alt)<>0
+		Local ctrl:=(event.Modifiers & Modifier.Control)<>0
+		Local shift:=(event.Modifiers & Modifier.Shift)<>0
+		Local menu:=(event.Modifiers & Modifier.Menu)<>0
 		
 		'ctrl+space - show autocomplete list
 		Select event.Type
@@ -187,18 +197,6 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 			Select key
 				
-'				#If __TARGET__="macos"
-'				Case Key.K
-'					If ctrl 
-'						If shift
-'							DeleteLineAtCursor()
-'						Else
-'							DeleteToEnd()
-'						Endif
-'						Return
-'					Endif
-'				#Endif
-				
 				Case Key.D
 					
 					Local ok:=ctrl
@@ -266,7 +264,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 						Endif
 						
 					Else
-						
+					
 						' remove all indent spaces by single press of Backspace
 						If Cursor=Anchor And Prefs.EditorUseSpacesAsTabs
 							
@@ -301,25 +299,23 @@ Class CodeDocumentView Extends Ted2CodeTextView
 							
 						Endif
 						
+						Local line:=CursorLine
+						If Cursor=Anchor And Cursor=Document.StartOfLine( line )
+							
+							Local f:=_folding[line]
+							If f And f.folded
+								_folding.Remove( line )
+								_folding[line-1]=f
+								f.startLine-=1
+								f.endLine-=1
+								f.folded+=10 ' hack
+							Endif
+						Endif
 						
 					Endif
 				
-				'Case Key.F11
-				'
-				'	ShowJsonDialog()
-					
-					
-				#If __TARGET__="windows"
-				Case Key.E 'delete whole line
-					If ctrl
-						DeleteLineAtCursor()
-						Return
-					Endif
-				#Endif
-			
-			
 				Case Key.X
-			
+					
 					If ctrl 'nothing selected - cut whole line
 						OnCut( Not CanCopy )
 						Return
@@ -327,7 +323,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 			
 				Case Key.C
-			
+					
 					If ctrl 'nothing selected - copy whole line
 						OnCopy( Not CanCopy )
 						Return
@@ -335,7 +331,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 			
 				Case Key.Insert 'ctrl+insert - copy, shift+insert - paste
-			
+					
 					If shift
 						SmartPaste()
 					Elseif ctrl
@@ -385,6 +381,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 					Local text:=Document.GetLine( line )
 					Local indent:=GetIndent( text )
 					Local posInLine:=PosInLineAtCursor
+					
 					'fix 'bug' when we delete ~n at the end of line.
 					'in this case GetLine return 2 lines, and if they empty
 					'then we get double indent
@@ -392,6 +389,15 @@ Class CodeDocumentView Extends Ted2CodeTextView
 					
 					Local beforeIndent:=(posInLine<=indent)
 					
+					If beforeIndent
+						Local f:=_folding[line]
+						If f And f.folded
+							_folding.Remove( line )
+							_folding[line+1]=f
+							SetLineVisible( line+1,True )
+						Endif
+					Endif
+					
 					If indent > posInLine Then indent=posInLine
 					
 					Local s:=(indent ? text.Slice( 0,indent ) Else "")
@@ -421,9 +427,9 @@ Class CodeDocumentView Extends Ted2CodeTextView
 							Endif
 						Endif
 					Endif
-			
+					
 					ReplaceText( "~n"+s )
-			
+					
 					Return
 			
 				#If __TARGET__="macos"
@@ -443,7 +449,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 				Case Key.Up '
 			
-					If event.Modifiers & Modifier.Menu
+					If menu
 						If shift 'selection
 							SelectText( 0,Anchor )
 						Else
@@ -454,7 +460,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 				Case Key.Down '
 			
-					If event.Modifiers & Modifier.Menu
+					If menu
 						If shift 'selection
 							SelectText( Anchor,Text.Length )
 						Else
@@ -831,6 +837,10 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		_lineColor=App.Theme.GetColor( "textview-cursor-line" )
 		
+	End
+	
+	Method OnValidateStyle() Override
+		
 		Local newFont:Font
 		Local fontPath:=Prefs.GetCustomFontPath()
 		If fontPath
@@ -840,6 +850,9 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		If Not newFont Then newFont=App.Theme.GetStyle( Style.Name ).Font
 		
 		RenderStyle.Font=newFont
+		
+		Super.OnValidateStyle()
+		
 	End
 	
 	Method InsertLiveTemplate:Bool( ident:String,templ:String=Null )
@@ -1135,8 +1148,21 @@ Class CodeDocument Extends Ted2Document
 				Endif
 			Endif
 			
-			result+="~n"+Utils.RepeatStr( "~t",indent+1 )+"~n"
-			result+=Utils.RepeatStr( "~t",indent )+"End"
+			Local indentStr:=TextUtils.GetIndentStr()
+			result+="~n"+Utils.RepeatStr( indentStr,indent+1 )+"~n"
+			result+=Utils.RepeatStr( indentStr,indent )+"End"
+		
+			Return result
+		Endif
+		
+		If ident="monkeydoc"
+			
+			Local indent:=Utils.GetIndent( textLine )
+			Local indentStr:=TextUtils.GetIndentStr()
+			indentStr=Utils.RepeatStr( indentStr,indent )
+			Local result:="#Rem monkeydoc ~n"
+			result+=indentStr+"#End"
+			
 			Return result
 		Endif
 		
@@ -1284,7 +1310,7 @@ Class CodeDocument Extends Ted2Document
 		
 		If Not ident Then ident=_codeView.IdentBeforeCursor()
 		
-		Print "ident: "+ident
+		'Print "ident: "+ident
 		
 		'show
 		Local lineNum:=TextDocument.FindLine( _codeView.Cursor )
@@ -1565,6 +1591,46 @@ Class CodeDocument Extends Ted2Document
 		OnUpdateCurrentScope()
 	End
 	
+	Field _tmpFileItems:=New Stack<CodeItem>
+	Method UpdateFolding()
+		
+		' extract all items in file
+		Local list:=_parser.ItemsMap[Path]
+		If list Then _tmpFileItems.AddAll( list )
+		
+		' extensions are here too
+		For list=Eachin _parser.ExtraItemsMap.Values
+			For Local i:=Eachin list
+				If i.FilePath=Path
+					If Not _tmpFileItems.Contains( i.Parent ) Then _tmpFileItems.Add( i.Parent )
+				Endif
+			Next
+		Next
+		
+		If _tmpFileItems.Empty
+			_codeView.ResetFolding()
+			Return
+		Endif
+		
+		UpdateFolding( _tmpFileItems,Null )
+		
+		_tmpFileItems.Clear()
+		
+	End
+	
+	Method UpdateFolding( items:Stack<CodeItem>,parent:CodeTextView.Folding )
+	
+		For Local i:=Eachin items
+			Local cls:=i.IsLikeClass
+			If cls Or i.IsLikeFunc Or i.IsOperator Or i.IsProperty
+				Local starts:=i.ScopeStartPos.x
+				Local ends:=i.ScopeEndPos.x
+				_codeView.MarkAsFoldable( starts,ends,parent )
+				If cls And i.Children Then UpdateFolding( i.Children,_codeView.GetFolding( starts ) )
+			Endif
+		Next
+	End
+	
 	Method InitParser()
 		
 		_parser=ParsersManager.Get( FileExtension )
@@ -1660,7 +1726,7 @@ Class CodeDocument Extends Ted2Document
 		Endif
 		
 		UpdateCodeTree()
-		
+		UpdateFolding()
 	End
 	
 	Method OnTextChanged()

+ 56 - 49
document/SceneDocument.monkey2

@@ -1,8 +1,13 @@
-
 Namespace ted2go
 
 #Import "<mojo3d>"
 #Import "<mojo3d-loaders>"
+#Import "<reflection>"
+
+'just too brutal in debug mode!
+#If __RELEASE__
+#Reflect mojo3d
+#Endif
 
 Using mojo3d..
 
@@ -18,38 +23,31 @@ Class SceneDocumentView Extends View
 	
 	Method OnRender( canvas:Canvas ) Override
 	
-		For Local x:=0 Until Width Step 64
-			For Local y:=0 Until Height Step 64
-				canvas.Color=(x~y) & 64 ? New Color( .1,.1,.1 ) Else New Color( .05,.05,.05 )
-				canvas.DrawRect( x,y,64,64 )
-			Next
-		Next
+		RequestRender()
 		
-		Local model:=_doc.Model
-		If Not model
-			canvas.Clear( Color.Sky )
+		If Not _doc.Scene Or Not _doc.Camera
+			canvas.Clear( Color.Red )
 			Return
 		Endif
 		
-		RequestRender()
-		
-		Global _anim:Float=0
-		
-		If Keyboard.KeyDown( Key.A )
-			If _doc.Model.Animator
+		If _doc.Model And _doc.Model.Animator
+			Global _anim:Float=0
+			If Keyboard.KeyDown( Key.A )
 				_anim+=12.0/60.0
 				_doc.Model.Animator.Animate( 0,_anim )
+			Else
+				_anim=0
 			Endif
-		Else
-			_anim=0
 		Endif
 		
+		_doc.Scene.Update()
+		
 		_doc.Camera.Render( canvas )
 	End
 	
 	Method OnMouseEvent( event:MouseEvent ) Override
 		
-		If Not _doc.Model Return
+		If Not _doc.Camera Or Not _doc.Model Return
 		
 		Global _v:Vec2i
 		Global _f:Bool
@@ -75,13 +73,15 @@ Class SceneDocumentView Extends View
 	
 	Method OnKeyEvent( event:KeyEvent ) Override
 		
+		If Not _doc.Camera Or Not _doc.Model Return
+		
 		If event.Type=EventType.KeyDown
 			Select event.Key
 			Case Key.R
 				_doc.Camera.Position=New Vec3f(0,0,-2.5)
 				_doc.Model.Rotation=New Vec3f(0,0,0)
 			Case Key.S
-				_doc.Light.CastsShadow=Not _doc.Light.CastsShadow
+				If _doc.Light _doc.Light.CastsShadow=Not _doc.Light.CastsShadow
 			Case Key.A
 				
 			End
@@ -100,22 +100,6 @@ Class SceneDocument Extends Ted2Document
 		Super.New( path )
 		
 		_view=New SceneDocumentView( Self )
-		
-		_scene=New Scene
-		
-		Scene.SetCurrent( _scene )
-		
-		_camera=New Camera( _view )
-		_camera.Near=.01
-		_camera.Far=10
-		_camera.MoveZ( -2.5 )
-		
-		_light=New Light
-		_light.RotateX( Pi/2 )
-		
-		_model=null
-		
-		Scene.SetCurrent( Null )
 	End
 	
 	Property Scene:Scene()
@@ -128,30 +112,51 @@ Class SceneDocument Extends Ted2Document
 		Return _camera
 	End
 	
-	Property Light:Light()
-		
-		Return _light
-	End
-	
 	Property Model:Model()
 	
 		Return _model
 	End
 	
+	Property Light:Light()
+		
+		Return _light
+	End
+	
 	Protected
 	
 	Method OnLoad:Bool() Override
 		
-		If _model _model.Destroy()
+		If ExtractExt( Path )=".mojo3d"		
+			
+			Print "Loading scene from "+Path
+			
+			_scene=Scene.Load( Path )
+			
+			_camera=Cast<Camera>( _scene.FindEntity( "Camera" ) )
+			
+			If _camera _camera.View=_view
+				
+			Return True
+		Endif
+		
+		_scene=New Scene
 		
 		Scene.SetCurrent( _scene )
+
+		_camera=New Camera( _view )
+		_camera.Near=.01
+		_camera.Far=10
+		_camera.MoveZ( -2.5 )
+		_camera.AddComponent<FlyBehaviour>()
+
+		_light=New Light
+		_light.RotateX( Pi/2 )
 		
 		_model=Model.Load( Path )
-		
-		Scene.SetCurrent( Null )
-
 		If _model _model.Mesh.FitVertices( New Boxf( -1,1 ) )
-	
+			
+		Scene.SetCurrent( Null )
+		
 		Return True
 	End
 	
@@ -163,7 +168,6 @@ Class SceneDocument Extends Ted2Document
 	Method OnClose() Override
 		
 		_scene.DestroyAllEntities()
-		
 	End
 	
 	Method OnCreateView:SceneDocumentView() Override
@@ -191,7 +195,11 @@ Class SceneDocumentType Extends Ted2DocumentType
 	Method New()
 		AddPlugin( Self )
 		
-		Extensions=New String[]( ".gltf",".glb",".b3d",".3ds",".obj",".dae",".fbx",".blend",".x" )
+		#If __RELEASE__
+			Extensions=New String[]( ".mojo3d",".gltf",".glb",".b3d",".3ds",".obj",".dae",".fbx",".blend",".x" )
+		#Else
+			Extensions=New String[]( ".gltf",".glb",".b3d",".3ds",".obj",".dae",".fbx",".blend",".x" )
+		#Endif
 	End
 	
 	Method OnCreateDocument:Ted2Document( path:String ) Override
@@ -202,5 +210,4 @@ Class SceneDocumentType Extends Ted2DocumentType
 	Private
 	
 	Global _instance:=New SceneDocumentType
-	
 End

+ 15 - 3
parser/CodeItem.monkey2

@@ -226,6 +226,16 @@ Class CodeItem
 		Return False
 	End
 	
+	Property IsOperator:Bool()
+	
+		Return _kind=CodeItemKind.Operator_
+	End
+	
+	Property IsProperty:Bool()
+	
+		Return _kind=CodeItemKind.Property_
+	End
+	
 	Property IsExtension:Bool()
 		Return _isExtension
 	Setter( value:Bool )
@@ -561,7 +571,7 @@ Struct CodeItemsSorter Final
 		
 		If _sorterByIdent = Null
 			_sorterByIdent=Lambda:Int( lhs:ListViewItem,rhs:ListViewItem )
-
+				
 				Local lp:=GetIdentPower( lhs.Text,_etalonIdent )
 				Local rp:=GetIdentPower( rhs.Text,_etalonIdent )
 				
@@ -575,7 +585,7 @@ Struct CodeItemsSorter Final
 		list.Sort( _sorterByIdent )
 	End
 	
-	Function GetIdentPower:Int( ident:String,etalon:String )
+	Function GetIdentPower:Int( ident:String,etalon:String,useStrongPrefs:Bool=True )
 		
 		Local len:=etalon.Length
 		Local power:=0
@@ -597,8 +607,10 @@ Struct CodeItemsSorter Final
 			Endif
 		Next
 		
+		If index<len Then power=0 ' not all letters found
+		
 		' strong first char
-		If Prefs.AcStrongFirstChar
+		If useStrongPrefs And Prefs.AcStrongFirstChar
 			Local lower1:=IsLowercacedFirstChar( ident )
 			Local lower2:=IsLowercacedFirstChar( etalon )
 			If lower1 <> lower2 ' if first chars cases aren't equals

+ 4 - 37
parser/Monkey2Parser.monkey2

@@ -794,7 +794,7 @@ Class Monkey2Parser Extends CodeParserPlugin
 	End
 	
 	Method AddExtensionItem( parent:CodeItem,item:CodeItem )
-	
+		
 		Local key:=parent.Ident
 		Local list:=ExtraItemsMap[key]
 		If Not list
@@ -813,7 +813,7 @@ Class Monkey2Parser Extends CodeParserPlugin
 	
 	Method RemoveExtensions( filePath:String )
 		
-		For Local list:=Eachin ExtraItemsMap.Values.All()
+		For Local list:=Eachin ExtraItemsMap.Values
 			Local it:=list.All()
 			While Not it.AtEnd
 				Local i:=it.Current
@@ -827,18 +827,9 @@ Class Monkey2Parser Extends CodeParserPlugin
 	End
 	
 	Method ExtractExtensionItems( item:CodeItem,target:Stack<CodeItem> )
-	
-		If ExtraItemsMap.Empty Return
 		
-		Local type:=item.Type.ident
-		Local list:=ExtraItemsMap[type]
-		If Not list
-			type=item.Ident
-			list=ExtraItemsMap[type]
-		Endif
-		If list
-			AddItems( list,target,True )
-		Endif
+		' use global function not a method
+		ted2go.ExtractExtensionItems( _extensions,item,target )
 	End
 	
 	Method StartParsing:String( pathOnDisk:String )
@@ -1146,30 +1137,6 @@ Class Monkey2Parser Extends CodeParserPlugin
 		Return False
 	End
 	
-	Method AddItems( items:Stack<CodeItem>,target:Stack<CodeItem>,checkUnique:Bool )
-		
-		If Not items Return
-		
-		If checkUnique' need to add unique
-			For Local i:=Eachin items
-		
-				Local s:=i.Text
-				Local exists:=False
-				For Local ii:=Eachin target
-					If ii.Text = s
-						exists=True
-						Exit
-					Endif
-				End
-				If Not exists
-					target.Add( i )
-				Endif
-			Next
-		Else
-			target.AddAll( items )
-		Endif
-	End
-	
 	Method GetAllItems( item:CodeItem,target:Stack<CodeItem>,isSuper:Bool=False )
 		
 		Local checkUnique:=Not target.Empty

+ 42 - 0
parser/Parser.monkey2

@@ -60,12 +60,54 @@ End
 
 
 Function StripGenericType:String( ident:String )
+	
 	Local i:=ident.Find("<")
 	If i > 0 Return ident.Slice( 0,i )
 	Return ident
 End
 
 
+Function AddItems( items:Stack<CodeItem>,target:Stack<CodeItem>,checkUnique:Bool )
+
+	If Not items Return
+
+	If checkUnique' need to add unique
+		For Local i:=Eachin items
+
+			Local s:=i.Text
+			Local exists:=False
+			For Local ii:=Eachin target
+				If ii.Text = s
+					exists=True
+					Exit
+				Endif
+			End
+			If Not exists
+				target.Add( i )
+			Endif
+		Next
+	Else
+		target.AddAll( items )
+	Endif
+End
+
+
+Function ExtractExtensionItems( extMap:StringMap<Stack<CodeItem>>,item:CodeItem,target:Stack<CodeItem> )
+
+	If extMap.Empty Return
+
+	Local type:=item.Type.ident
+	Local list:=extMap[type]
+	If Not list
+		type=item.Ident
+		list=extMap[type]
+	Endif
+	If list
+		AddItems( list,target,True )
+	Endif
+End
+
+
 Class ParserRequestOptions Final
 	
 	Field ident:String

+ 4 - 0
product/Mx2ccEnv.monkey2

@@ -55,6 +55,8 @@ End
 Public
 
 Function LoadEnv()
+	
+	Local e_path:=GetEnv( "PATH" )
 
 	Local path:="bin/env_"+HostOS+".txt"
 
@@ -83,6 +85,8 @@ Function LoadEnv()
 		SetEnv( name,value )
 	Next
 	
+	SetEnv( "PATH",e_path )
+	
 	Local moddirs:=New StringStack
 	moddirs.Add( CurrentDir()+"modules/" )
 	For Local moddir:=Eachin GetEnv( "MX2_MODULE_DIRS" ).Split( ";" )

+ 1 - 1
testing/ParserTests.monkey2

@@ -188,7 +188,7 @@ End
 
 
 Class Test2 Extends TestClass
-
+	
 	Function Fff( tt:TestClass,cc:Canvas )
 		tt.MyFuncPub()
 		

+ 5 - 0
utils/JsonUtils.monkey2

@@ -53,6 +53,11 @@ Function Json_GetInt:Int( json:Map<String,JsonValue>,key:String,def:Int )
 	Return json.Contains( key ) ? Int(json[key].ToNumber()) Else def
 End
 
+Function Json_GetFloat:Float( json:Map<String,JsonValue>,key:String,def:Float )
+	
+	Return json.Contains( key ) ? Float(json[key].ToNumber()) Else def
+End
+
 
 
 Class JsonArray Extension

+ 5 - 0
utils/TextUtils.monkey2

@@ -14,6 +14,11 @@ Class TextUtils Final
 		Return _spacesForTab
 	End
 	
+	Function GetIndentStr:String()
+		
+		Return Prefs.EditorUseSpacesAsTabs ? GetSpacesForTabEquivalent() Else "~t"
+	End
+	
 	Function GetPosInLineCheckingTabSize:Int( line:String,posInLine:Int,tabSize:Int )
 	
 		Local pos:=0

+ 19 - 5
view/AutocompleteView.monkey2

@@ -527,11 +527,13 @@ Class AutocompleteDialog Extends NoTitleDialog
 		Next
 		'preprocessor
 		'need to load it like keywords
-		Local s:="#If ,#Rem,#End,#Endif,#Else,#Else If ,#Import ,#Reflect ,monkeydoc,__TARGET__,__MOBILE_TARGET__,__DESKTOP_TARGET__,__WEB_TARGET__,__HOSTOS__,__ARCH__,__DEBUG__,__RELEASE__,__CONFIG__,__MAKEDOCS__"
-		Local arr:=s.Split( "," )
-		For Local i:=Eachin arr
-			list.Add( New ListViewItem( i ) )
-		Next
+		Local s:=GetPreprocessorDirectives( fileType )
+		If s
+			Local arr:=s.Split( "," )
+			For Local i:=Eachin arr
+				list.Add( New ListViewItem( i ) )
+			Next
+		Endif
 		_keywords[fileType]=list
 	End
 	
@@ -556,4 +558,16 @@ Class AutocompleteDialog Extends NoTitleDialog
 		_parsers[fileType]=ParsersManager.Get( fileType )
 	End
 	
+	Function GetPreprocessorDirectives:String( fileType: String )
+		
+		Select fileType
+			Case ".monkey2"
+				Return "#If ,#Rem,#End,#Endif,#Else,#Else If ,#Import ,#Reflect ,monkeydoc,__TARGET__,__MOBILE_TARGET__,__DESKTOP_TARGET__,__WEB_TARGET__,__HOSTOS__,__ARCH__,__DEBUG__,__RELEASE__,__CONFIG__,__MAKEDOCS__"
+			Case ".cpp",".h",".hpp",".c"
+				Return "#if ,#end,#endif,#else,#elif ,#define ,#undef ,#ifdef ,#ifndef "
+		End
+		
+		Return ""
+	End
+	
 End

+ 113 - 25
view/CodeGutterView.monkey2

@@ -5,10 +5,10 @@ Namespace ted2go
 Class CodeGutterView Extends View
 
 	Method New( doc:CodeDocument )
+		
 		Style=GetStyle( "GutterView" )
 	
-		_doc = doc
-		'_textView = doc.TextView
+		_doc=doc
 	End
 	
 	Protected
@@ -16,10 +16,11 @@ Class CodeGutterView Extends View
 	Method OnValidateStyle() Override
 	
 		Local font:=RenderStyle.Font
-	
-		_width=font.TextWidth( "1234567" )
+		Local k:=App.Theme.Scale.x
+		
+		_width=font.TextWidth( "1234567" )+10*k
 
-		_size=New Vec2i( font.TextWidth( "12345678" ),0 )
+		_size=New Vec2i( _width+8*k,0 )
 	End
 	
 	Method OnMeasure:Vec2i() Override
@@ -28,8 +29,8 @@ Class CodeGutterView Extends View
 	End
 	
 	Method OnRender( canvas:Canvas ) Override
-	
-		_textView=_doc.TextView
+		
+		_textView=_doc.CodeView
 		
 		Local cursorLine:=_textView.Document.FindLine( _textView.Cursor )
 		Local anchorLine:=_textView.Document.FindLine( _textView.Anchor )
@@ -43,7 +44,7 @@ Class CodeGutterView Extends View
 		Local vrect:=_textView.VisibleRect
 		
 		Local firstLine:=_textView.LineAtPoint( vrect.TopLeft )
-
+		
 		Local lastLine:=_textView.LineAtPoint( vrect.BottomLeft )+1
 		
 		canvas.Translate( 0,-vrect.Top )
@@ -52,56 +53,143 @@ Class CodeGutterView Extends View
 		
 		canvas.Color=textColor
 		
-		For Local i:=firstLine Until lastLine
+		Local curFolding:=FindNearestFoldingAbove( firstLine-1 )
+		_folded.Clear()
 		
-			Local rect:=_textView.LineRect( i )
+		Local k:=App.Theme.Scale.x
 		
-			Local ok:= Prefs.EditorShowEvery10LineNumber And ((i+1) Mod 10 <> 0)
+		For Local i:=firstLine Until lastLine
+			
+			If Not _textView.IsLineVisible( i )
+				Continue
+			Endif
+			
+			Local rect:=_textView.LineRect( i )
+			Local xx:=_width-8*k
+			Local hcenter:=rect.Top+RenderStyle.Font.Height*.5
+			' folding marks
+			'
+			Local folding:=_textView.GetFolding( i )
+			If folding
+				Local dx:=-2*k
+				canvas.Color=textColor
+				canvas.Alpha=1
+				canvas.DrawLine( xx+7*k+dx,hcenter,xx+7*k+7*k+dx,hcenter ) ' horiz line
+				If folding.folded
+					canvas.DrawLine( xx+10*k+dx,hcenter-4*k,xx+10*k+dx,hcenter-4*k+8*k ) ' vert line to make cross
+				Endif
+				canvas.DrawRectWire( xx+5*k+dx,hcenter-5*k,10*k,10*k ) ' bounding rect
+				If Not folding.folded
+					If curFolding Then _folded.Add( curFolding )
+					curFolding=folding
+					Local dy:=rect.Height-RenderStyle.Font.Height
+					If dy>0
+						dx=8*k
+						canvas.Alpha=0.5
+						canvas.DrawLine( xx+dx,rect.Bottom-dy,xx+dx,rect.Bottom )
+						canvas.Alpha=1
+					Endif
+				Endif
+			Elseif curFolding
+				canvas.Color=textColor
+				canvas.Alpha=0.5
+				Local dx:=8*k
+				If i=curFolding.endLine
+					If _folded.Empty
+						curFolding=Null
+					Else
+						curFolding=_folded[0]
+						_folded.Erase( 0 )
+					Endif
+					canvas.DrawLine( xx+dx,rect.Top,xx+dx,hcenter )
+					canvas.DrawLine( xx+dx,hcenter,xx+dx+6*k,hcenter )
+					If curFolding
+						canvas.DrawLine( xx+8*k,hcenter,xx+8*k,rect.Bottom )
+					Endif
+				Else
+					canvas.DrawLine( xx+dx,rect.Top,xx+dx,rect.Bottom )
+				Endif
+				canvas.Alpha=1
+			Endif
+			
+			' show dots between each 10th
+			'
+			Local ok:=Prefs.EditorShowEvery10LineNumber And ((i+1) Mod 10 <> 0)
+			ok=ok And Not (folding And folding.folded)
 			If ok And i<>cursorLine And i<>anchorLine
 				canvas.Alpha=0.5
-				canvas.DrawRect( _width-4,rect.Top+rect.Height*.5-1,2,2 )
+				canvas.DrawRect( xx-4*k,hcenter-1*k,2*k,2*k )
 				canvas.Alpha=1
 				Continue
 			Endif
 			
 			' show error bubble
-			
+			'
 			If _doc.HasErrors And _doc.HasErrorAt( i )
 				If _errorIcon <> Null
 					canvas.Color=Color.White
-					canvas.DrawImage( _errorIcon,_width-_errorIcon.Width,rect.Top )
+					canvas.DrawImage( _errorIcon,xx-_errorIcon.Width,rect.Top )
 					canvas.Color=textColor
 				Endif
 			Else
 				canvas.Color=(i=cursorLine Or i=anchorLine) ? textColor*1.125 Else textColor 'make selected line number little brighter
-				canvas.DrawText( i+1,_width,rect.Top+rect.Height*.5,1,.5 )
+				canvas.DrawText( i+1,xx,hcenter,1,.5 )
 			Endif
 			
 		Next
 		
+		canvas.Alpha=1
+		
 	End
 	
-	#Rem
-	Method OnContentMouseEvent( event:MouseEvent ) Override
+	Method OnMouseEvent( event:MouseEvent ) Override
 		
-		Select event.Type
-			Case EventType.MouseMove
-				
+		If event.Type=EventType.MouseUp
 			
+			Local pos:=event.Location-New Vec2i( 0,4*App.Theme.Scale.y )
+			If pos.x<_width-6*App.Theme.Scale.x Return
+			
+			Local line:=_textView.LineAtPoint( pos+New Vec2i( 100,_textView.Scroll.y ) )
+			
+			_textView.SwitchFolding( line,True )
 		End
-		
-		Super.OnContentMouseEvent( event )
-		
 	End
-	#End
+	
 	
 	Private
 	
 	Field _width:Int
 	Field _size:Vec2i
-	Field _textView:TextView
+	Field _textView:CodeDocumentView
 	Field _doc:CodeDocument
+	Field _folded:=New Stack<CodeTextView.Folding>
 	
 	Global _errorIcon:Image
 	
+	Method FindNearestFoldingAbove:CodeTextView.Folding( line:Int )
+		
+		For Local i:=line To 0 Step -1
+			Local folding:=_textView.GetFolding( i )
+			If folding
+				Return folding.endLine>line ? folding Else Null
+			Endif
+		Next
+		
+		Return Null
+	End
+	
 End
+
+
+Class Canvas Extension
+	
+	Method DrawRectWire( x:Float,y:Float,w:Float,h:Float )
+		
+		Self.DrawLine( x,y,x+w,y )
+		Self.DrawLine( x+w,y,x+w,y+h )
+		Self.DrawLine( x+w,y+h,x,y+h )
+		Self.DrawLine( x,y+h,x,y )
+	End
+	
+End
+

+ 308 - 43
view/CodeTextView.monkey2

@@ -11,6 +11,13 @@ Class CodeTextView Extends TextView
 	Field LineNumChanged:Void( prevLine:Int,newLine:Int )
 	Field TextChanged:Void()
 	
+	Class Folding
+		Field folded:Int
+		Field startLine:Int
+		Field endLine:Int
+		Field parent:Folding
+	End
+	
 	Method New()
 		
 		Super.New()
@@ -22,46 +29,89 @@ Class CodeTextView Extends TextView
 		Document.TextChanged += TextChanged
 		
 		TabStop=Prefs.EditorTabSize
+		LineSpacing=Prefs.EditorLineSpacing
+		
+		Document.LinesModified+=Lambda( first:Int,removed:Int,inserted:Int )
+			
+			Local delta:=inserted-removed
+			If delta=0 Return
+			
+			'Local prevLine:=Document.FindLine( _storedCursor )
+			
+			'Print "modif line: "+first+", "+removed+", "+inserted
+			
+			' line with folding was removed
+			'
+			If removed<>0
+				Local i1:=Min( first,first+removed )
+				Local i2:=Max( first,first+removed )
+				For Local i:=i1 Until i2
+					Local f:=_folding[i]
+					If Not f Continue
+					If f.folded<10
+						_folding.Remove( i )
+					Else
+						f.folded-=10
+						UpdateLineWidth( i ) ' dirty
+					Endif
+				Next
+			Endif
+			
+			
+			Local flag:=False
+			Local indent:=Utils.GetIndent( LineTextAtCursor )
+			Local less:=(PosInLineAtCursor<=indent)
+			
+			For Local line:=Eachin _folding.Keys
+				
+				Local f:=_folding[line]
+				
+				' move foldings which are under changed line
+				Local shiftBlock:=False
+				Local expandBlock:=False
+				
+				If line>=first
+				
+					If line>first Or less ' shift down whole block
+						shiftBlock=True
+					Else
+						expandBlock=True
+					Endif
+					
+				Elseif first>f.startLine And first<=f.endLine ' changed line is inside of folding
+					
+					expandBlock=(first<f.endLine Or less) ' expand block ending
+					
+				Endif
+				
+				If shiftBlock ' shift down whole block
+					If f.folded
+						flag=True
+					Endif
+					f.startLine+=delta
+					f.endLine+=delta
+					_foldingTmpMap[line]=f
+					
+				Elseif expandBlock
+					f.endLine+=delta
+				Endif
+				
+			Next
+			
+			For Local line:=Eachin _foldingTmpMap.Keys
+				Local f:=_foldingTmpMap[line]
+				_folding.Remove( line )
+				_folding[f.startLine]=f
+			Next
+			_foldingTmpMap.Clear()
+			
+			If flag
+				OnValidateStyle()
+				RequestRender()
+			Endif
+			
+		End
 		
-'		LineNumChanged+=Lambda( prevLine:Int,newLine:Int )
-'		
-'		End
-		
-		
-'		Document.LinesModified += Lambda( first:Int,removed:Int,inserted:Int )
-'			
-'			If _extraSelStart=-1 Return
-'			If first>=_extraSelEnd Print "ret" ; Return
-'			
-'			Print "LinesModified: "+first+", "+removed+", "+inserted
-'			
-'			If inserted>0
-'				
-'				If first<_extraSelStart
-'					Print "if 1-1"
-'					_extraSelStart+=inserted
-'				Endif
-'				_extraSelEnd+=inserted
-'				
-'			Else
-'				
-'				If first<=_extraSelStart And first+removed>=_extraSelEnd
-'					ResetExtraSelection()
-'					Print "reset"
-'					Return
-'				Endif
-'				
-'				If first<_extraSelStart
-'					Print "if 2-1"
-'					_extraSelStart-=removed
-'					_extraSelEnd-=removed
-'				Else
-'					Print "if 2-2"
-'					_extraSelEnd-=Min( removed,_extraSelEnd-first )
-'				Endif
-'				
-'			Endif
-'		End
 		
 		UpdateThemeColors()
 	End
@@ -319,6 +369,9 @@ Class CodeTextView Extends TextView
 	Property LineTextAtCursor:String()
 		Return Document.GetLine( Document.FindLine( Cursor ) )
 	End
+	Property LineTextAtAnchor:String()
+		Return Document.GetLine( Document.FindLine( Anchor ) )
+	End
 	
 	Property LineNumAtCursor:Int()
 		Return Document.FindLine( Cursor )
@@ -415,12 +468,197 @@ Class CodeTextView Extends TextView
 		
 	End
 	
+	Method ResetFolding()
+		
+		_folding.Clear()
+	End
+	
+	Method MarkAsFoldable( line:Int,endLine:Int,parent:Folding )
+		
+		' try to fix folding bounds - we get incorrect endLine from mx2cc
+		While endLine>line
+			' searching for End keyword with the same indentation as [line]
+			Local ok:=Document.GetLine( endLine ).Trim().ToLower().StartsWith( "end" )
+			If ok
+				ok = ok And Utils.GetIndent( Document.GetLine( line ) )=Utils.GetIndent( Document.GetLine( endLine ) )
+			Endif
+			If ok
+				' parser based on colors, OMG :)
+				Local color:=Document.Colors[Document.StartOfLine( endLine )]
+				ok = ok And Not (color=Highlighter.COLOR_COMMENT Or color=Highlighter.COLOR_STRING)
+			Endif
+			If ok
+				Exit
+			Endif
+			endLine-=1
+		Wend
+		
+		Local folding:Folding
+		If Not _folding.Contains( line )
+			
+			If endLine>line
+				folding=New Folding
+				folding.startLine=line
+				_folding[line]=folding
+			Endif
+			
+		Else
+			folding=_folding[line]
+		Endif
+		
+		If folding
+			folding.endLine=endLine
+			folding.parent=parent
+		Endif
+	End
+	
+	Method SwitchFolding( line:Int,gotoLine:Bool=False )
+		
+		If Not _folding.Contains( line ) Return
+		
+		If gotoLine
+			GotoLine( line )
+		Endif
+		
+		If _folding[line].folded
+			UnfoldBlock( line )
+		Else
+			FoldBlock( line )
+		Endif
+	End
+	
+	Method FoldBlock( startLine:Int,updateLines:Bool=True,findBlock:Bool=False )
+		
+		If findBlock
+			Local f:=FindNearestFolding( startLine )
+			If Not f Return
+			startLine=f.startLine
+		Endif
+		
+		If Not _folding.Contains( startLine ) Return
+		Local f:=_folding[startLine]
+		If f.folded Return
+		
+		f.folded=True
+		Local endLine:=f.endLine
+		Local curLine:=LineNumAtCursor
+		Local moveCursor:=False
+		
+		For Local line:=startLine+1 To endLine
+			SetLineVisible( line,False )
+			If line=curLine Then moveCursor=True
+		Next
+		
+		If moveCursor
+			Local pos:=Document.StartOfLine( startLine )
+			SelectText( pos,pos )
+		Endif
+		
+		If updateLines
+			OnValidateStyle()
+			RequestRender()
+		Endif
+	End
+	
+	Method UnfoldBlock( startLine:Int,updateLines:Bool=True,findBlock:Bool=False,unfoldChildren:Bool=False )
+		
+		If findBlock
+			Local f:=FindNearestFolding( startLine )
+			If Not f Return
+			startLine=f.startLine
+		Endif
+		
+		If Not _folding.Contains( startLine ) Return
+		If Not _folding[startLine].folded Return
+		
+		_folding[startLine].folded=False
+		Local endLine:=_folding[startLine].endLine
+		
+		For Local line:=startLine+1 To endLine
+			
+			SetLineVisible( line,True )
+			Local folding:=_folding[line]
+			If folding And folding.folded
+				line=folding.endLine
+			Endif
+		Next
+		
+		If updateLines
+			OnValidateStyle()
+			RequestRender()
+		Endif
+	End
+	
+	Method FindNearestFolding:Folding( line:Int )
+		
+		Local maxLine:=-1
+		Local found:Folding
+		For Local i:=Eachin _folding.Keys
+			If maxLine<>-1 And i>maxLine Exit
+			Local f:=_folding[i]
+			If line>=f.startLine And line<=f.endLine
+				found=f
+				maxLine=f.endLine
+			Endif
+			
+		Next
+	
+		Return found
+	End
+	
+	Method UnfoldAtLine( line:Int )
+		
+		' find folding block at line
+		Local found:=FindNearestFolding( line )
+		
+		If Not found Return
+		
+		' unfold nearest block
+		UnfoldBlock( found.startLine,False )
+		Local par:=found.parent
+		While par
+			' and all its parents
+			UnfoldBlock( par.startLine,False )
+			par=par.parent
+		Wend
+		
+		OnValidateStyle()
+		RequestRender()
+		
+	End
+	
+	Method GetFolding:Folding( line:Int )
+	
+		Return _folding.Contains( line ) ? _folding[line] Else Null
+	End
+	
+	Method FoldAll()
+	
+		For Local line:=Eachin _folding.Keys
+			FoldBlock( line,False )
+		Next
+		
+		OnValidateStyle()
+		RequestRender()
+	End
+	
+	Method UnfoldAll()
+	
+		For Local line:=Eachin _folding.Keys
+			UnfoldBlock( line,False )
+		Next
+		
+		OnValidateStyle()
+		RequestRender()
+	End
+	
 	
 	Protected
 	
+	Field _folding:=New IntMap<Folding>
+	
 	Method CheckFormat( event:KeyEvent )
 		
-		
 		Select event.Type
 		
 			Case EventType.KeyChar
@@ -722,6 +960,7 @@ Class CodeTextView Extends TextView
 		
 		_whitespacesColor=App.Theme.GetColor( "textview-whitespaces" )
 		_extraSelColor=App.Theme.GetColor( "textview-extra-selection" )
+		_commentsColor=App.Theme.GetColor( "textview-color"+Highlighter.COLOR_COMMENT )
 	End
 	
 	Method OnRenderContent( canvas:Canvas,clip:Recti ) Override
@@ -753,7 +992,21 @@ Class CodeTextView Extends TextView
 	Method OnRenderLine( canvas:Canvas,line:Int ) Override
 		
 		Super.OnRenderLine( canvas,line )
-	
+		
+		' show folded lines number
+		'
+		Local folding:=_folding[line]
+		If folding And folding.folded
+			Local r:=LineRect( line )
+			Local tx:=r.Right+20
+			Local ty:=r.Top+RenderStyle.Font.Height*.5
+			Local a:=canvas.Alpha
+			canvas.Alpha=0.75
+			canvas.Color=_commentsColor
+			canvas.DrawText( "..."+(folding.endLine-folding.startLine)+" line(s)",tx,ty,0,.5 )
+			canvas.Alpha=a
+		Endif
+		
 		' draw whitespaces
 		'
 		If Not _showWhiteSpaces Return
@@ -795,7 +1048,7 @@ Class CodeTextView Extends TextView
 			right=word.Rect.Right
 			
 		Next
-	
+		
 	End
 	
 	Method OnValidateStyle() Override
@@ -811,7 +1064,7 @@ Class CodeTextView Extends TextView
 	Private
 	
 	Field _line:Int
-	Field _whitespacesColor:Color
+	Field _whitespacesColor:Color,_commentsColor:Color
 	Field _showWhiteSpaces:Bool
 	Field _tabw:Int,_charw:Int
 	Field _overwriteMode:Bool
@@ -819,10 +1072,21 @@ Class CodeTextView Extends TextView
 	Field _extraSelColor:Color=Color.DarkGrey
 	Field _storedCursor:Int
 	Field _typing:Bool
+	Field _foldingTmpMap:=New IntMap<Folding>
 	
 	Method OnCursorMoved()
 		
 		Local line:=Document.FindLine( Cursor )
+		
+		' if cursor is inside of invisible line - unfold area with cursor
+		If Not IsLineVisible( line )
+			'Print "show line: "+line
+			Local c:=Cursor,a:=Anchor
+			UnfoldAtLine( line )
+			SelectText( a,c )
+		Endif
+		
+		' emit line changed signal
 		If line <> _line
 			If _typing Then FormatLine( _line )
 			
@@ -894,6 +1158,7 @@ Function RemoveWhitespacedTrailings:String( doc:TextDocument,linesChanged:Int Pt
 		Local start:=doc.StartOfLine( line )
 		Local ends:=doc.EndOfLine( line )
 		Local i:=ends-1
+		If i<0 Continue
 		
 		Local color:=doc.Colors[i]
 		If color=Highlighter.COLOR_STRING Or color=Highlighter.COLOR_COMMENT Continue

+ 1 - 1
view/CodeTreeView.monkey2

@@ -24,7 +24,7 @@ Class CodeTreeView Extends TreeViewExt
 		If list Then _stack.AddAll( list )
 		
 		' extensions are here too
-		For Local lst:=Eachin parser.ExtraItemsMap.Values.All()
+		For Local lst:=Eachin parser.ExtraItemsMap.Values
 			For Local i:=Eachin lst
 				If i.FilePath=path
 					If Not _stack.Contains( i.Parent ) Then _stack.Add( i.Parent )

+ 15 - 3
view/ConsoleViewExt.monkey2

@@ -114,7 +114,7 @@ Class ConsoleExt Extends TextView
 				Return
 			Endif
 			
-			stdout=_stdout+(stdout.Replace( "~r~n","~n" ).Replace( "~r","~n" ))
+			stdout=_stdout+stdout.Replace( "~r~n","~n" )
 			
 			Local i0:=0
 			Repeat
@@ -376,11 +376,23 @@ Class ConsoleExt Extends TextView
 			Local sc:=Scroll
 			Local maxScroll:=LineRect(Document.NumLines-1).Bottom-VisibleRect.Height
 			Local atBottom:=Scroll.y>=maxScroll And cur=anc
-			AppendText( text )
-			SelectText( Text.Length,Text.Length )
+			
+			If text.StartsWith( "~r" )
+				Local line:=Document.NumLines-1,dl:=0
+				If Document.GetLine( line )=""
+					dl=-1
+				Endif
+				SelectText( Document.StartOfLine( line+dl ),Document.EndOfLine( line ) )
+				ReplaceText( text.Replace( "~r","" ) )
+			Else
+				AppendText( text )
+			Endif
+			
 			If Not atBottom
 				SelectText( anc,cur )
 				Scroll=sc 'restore
+			Else
+				SelectText( Text.Length,Text.Length )
 			Endif
 			
 		Endif

+ 14 - 7
view/DraggableViewListener.monkey2

@@ -23,7 +23,7 @@ Interface IDraggableHolder
 	
 End
 
-#Rem Call order: Detach -> OnDragStarted -> Attach -> OnDragEnded
+#Rem Calls order: Detach -> OnDragStarted -> Attach -> OnDragEnded
 
 #End
 Class DraggableViewListener<TItem,THolder>
@@ -76,12 +76,7 @@ Class DraggableViewListener<TItem,THolder>
 				If Not _item Return
 				
 				If _detached
-					Local r:=_view.Frame
-					Local sz:=r.Size
-					r.TopLeft=Mouse.Location+New Vec2i( 0,-10 )
-					r.BottomRight=r.TopLeft+sz
-					_view.Frame=r
-					App.RequestRender()
+					UpdateDetached()
 					Return
 				Endif
 				
@@ -89,6 +84,7 @@ Class DraggableViewListener<TItem,THolder>
 				
 				If dy>=_threshold*App.Theme.Scale.y
 					Detach()
+					UpdateDetached()
 				Endif
 				
 			
@@ -118,6 +114,7 @@ Class DraggableViewListener<TItem,THolder>
 				_item=Null
 				_detached=False
 				
+				event.Eat()
 		End
 	
 	End
@@ -142,6 +139,16 @@ Class DraggableViewListener<TItem,THolder>
 		
 	End
 	
+	Method UpdateDetached()
+		
+		Local r:=_view.Frame
+		Local sz:=r.Size
+		r.TopLeft=Mouse.Location+New Vec2i( 0,-10 )
+		r.BottomRight=r.TopLeft+sz
+		_view.Frame=r
+		App.RequestRender()
+	End
+	
 	Function CanAttach:Bool( item:TItem,holder:THolder )
 		
 		If Not holder Return False

+ 0 - 1538
view/IRCView.monkey2

@@ -1,1538 +0,0 @@
-'IRC module for Monkey 2 by @Hezkore
-'https://github.com/Hezkore/IRC-Monkey2
-
-Namespace ted2go
-
-'=MODULE=
-
-'This class is the main class
-'It may contain multiple server connections at once
-Class IRC
-	Field servers:=New List<IRCServer>
-	
-	'When a new message is received
-	Field OnMessage:Void(message:IRCMessage,container:IRCMessageContainer,server:IRCServer)
-	
-	'When the userlist's been updated
-	Field OnUserUpdate:Void(container:IRCMessageContainer,server:IRCServer)
-	
-	'When a new message container is created
-	Field OnNewContainer:Void(container:IRCMessageContainer,server:IRCServer)
-	
-	'When a message container is removed
-	Field OnRemoveContainer:Void(container:IRCMessageContainer,server:IRCServer)
-	
-	'Add and connect to a new IRC server
-	'Nickname is your user name to connect with
-	'Name/Description of server, can by anything you want
-	'Server is IP/address
-	'Port is server... port...
-	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
-		Local nS:=New IRCServer(nickname,name,server,port)
-		nS.parent=Self
-		nS.type=ContainerType.Server
-		servers.AddLast(nS)
-		OnNewContainer(nS,nS)
-		Return nS
-	End
-End
-
-'Message container types
-Enum ContainerType 
-	None=0
-	Server=1	'Is the server message container
-	Room=2		'Is a room message container
-	User=3		'Is a private chat between two users
-End
-
-'This class acts as a single IRC server connection
-'It contains "message containers", such as rooms and private messages from users
-'The server itself is also a message container
-Class IRCServer Extends IRCMessageContainer
-	Field parent:IRC
-	Field serverAddress:String
-	Field serverPort:Int
-	Field sendBuffer:DataBuffer
-	Field receiveBuffer:DataBuffer=New DataBuffer(512)
-	Field fiberSleep:Float=0.35 'Lower value gets internet messages faster but uses more CPU
-	Field updateFiber:Fiber 'Fiber for updating internet mesages
-	Field socket:Socket
-	Field stream:SocketStream
-	Field nickname:String
-	Field realname:String="KoreIRC 1.0"
-	Field messageContainers:=New List<IRCMessageContainer>
-	Field skipContainers:=New String[]("chanserv","nickserv","services","*") 'Make sure these are lower case!
-	
-	Field autoJoinRooms:String
-	Field hasAutoJoinedRooms:Bool
-	
-	Field accepted:Bool
-	
-	Property AutoJoinRooms:String()
-		Return autoJoinRooms
-	Setter(str:String)
-		autoJoinRooms=str
-	End
-	
-	Property Connected:Bool()
-		If stream And socket And Not socket.Closed Return True
-		Return False
-	End
-	
-	'Is this a container we should skip?
-	Method SkipContainer:Bool(compare:String,alsoSkip:String="")
-		compare=compare.ToLower()
-		
-		'Skip containers for the server host name (user server name instead)
-		If compare=socket.PeerAddress.Host.ToLower() Return True
-		
-		'Skip one extra thing, like own username
-		If compare=alsoSkip.ToLower() Return True
-		
-		'Skip the defined skip containers
-		For Local s:=Eachin skipContainers
-			If s=compare Then Return True
-		Next
-		
-		'Skip nothing!
-		Return False
-	End
-	
-	Method Disconnect()
-		If socket Then socket.Close()
-		If stream Then stream.Close()
-		
-		For Local mC:=Eachin Self.messageContainers
-			Self.RemoveMessageContainer(mC)
-		Next
-	End
-	
-	Method Remove() Override
-		parent.servers.Remove(Self)
-	End
-	
-	'Connect using existing nickname, server address and port
-	Method Connect()
-		If Connected Then Return
-		
-		TriggerOnMessage("Connecting to server "+Self.serverAddress+":"+Self.serverPort,Self.name,Self.name,null,Self)
-		
-		socket=Socket.Connect(Self.serverAddress,Self.serverPort)
-		If Not socket
-			TriggerOnMessage("Couldn't connect to server",Self.name,Self.name,null,Self)
-			Return
-		Endif
-		
-		'No delay pretty please
-		socket.SetOption("TCP_NODELAY",True)
-		
-		'Prepare stream
-		stream=New SocketStream(socket)
-		If Not stream
-			TriggerOnMessage("Couldn't create socket stream",Self.name,Self.name,null,Self)
-			Return
-		Endif
-		
-		'Start our update loop in the background
-		UpdateLoop()
-	End
-	
-	'New IRC server
-	Method New(nickname:String,name:String,server:String,port:Int,autoConnect:Bool=True)
-		Self.nickname=nickname
-		Self.name=name
-		Self.serverAddress=server
-		Self.serverPort=port
-		
-		If autoConnect Then Self.Connect()
-	End
-	
-	'Background update loop for internet messages
-	Method UpdateLoop()
-		updateFiber=New Fiber(Lambda()
-			
-			'Send register stuff at start
-			'PASSWORD HERE
-			SendString("USER"+" "+nickname+" "+socket.Address+" "+socket.PeerAddress+" :"+realname)
-			SendString("NICK"+" "+nickname)
-			
-			'Stuff we'll use later
-			Local lineSplit:String[]
-			Local recLine:String
-			Local recLength:Int
-			Local linePos:Int
-			Local line:String
-			Local data:String
-			Local msg:String
-			Local param:String[]
-			
-			'Never ending loop of internet data
-			While Not stream.Eof And Self.Connected
-				
-				'Try to auto join channels
-				If Not hasAutoJoinedRooms And accepted Then
-					hasAutoJoinedRooms=True
-					Local rooms:=autoJoinRooms.Split("#")
-					
-					For Local s:String=Eachin rooms
-						If s.Length>0 Then
-							SendString("JOIN #"+s)
-							Fiber.Sleep(fiberSleep)
-						Endif
-					Next
-					
-				Endif
-				
-				'Do we have anything to receive?
-				While socket.CanReceive
-					'How much should we receive, no more than our buffer length
-					recLength=Min(socket.CanReceive,receiveBuffer.Length)
-					socket.Receive(receiveBuffer.Data,recLength)
-					
-					'Add to our receive string until we've got a line ready
-					recLine+=receiveBuffer.PeekString(0,recLength).Replace("~r","")
-					While recLine.Contains("~n") 'We got a line!
-						'Remove processed line from receive string
-						linePos=recLine.Find("~n")
-						line=recLine.Left(linePos)
-						recLine=recLine.Slice(linePos+1)
-						
-						'Process our newly received line
-						
-						'Remove start : if it exists
-						If line.StartsWith(":") Then line=line.Slice(1)
-						
-						'Return any PING message instantly
-						If line.ToLower().StartsWith("ping ") Then
-							SendString("PONG "+line.Split(" ")[1])
-							Continue 'We're done with the ping message now
-						Endif
-						
-						'If we've got a : somewhere, it's probably a message
-						If line.Contains(":") Then
-							data=line.Split(":")[0]
-							msg=line.Split(data)[1]
-							If msg.StartsWith(":") Then msg=msg.Slice(1)
-						Else
-							data=line
-							msg=Null
-						Endif
-						
-						'Debug print message
-						'Print "MSG: "+data+">"+msg
-						
-						'Process the message we got
-						If data.Contains(" ") Then
-							param=data.Split(" ")
-							OnMessage(msg,param)
-						Endif
-						
-					Wend
-				Wend
-				
-				'Sleep if there's nothing more to receive
-				Fiber.Sleep(fiberSleep)
-			Wend
-			
-			TriggerOnMessage("Disconnected",Self.name,Self.name,null,Self)
-			If stream Then stream.Close()
-		End)
-	End
-	
-	Private
-		'A safe way to read from an array
-		Method GetParam:String(index:Int,param:String[])
-			If param.Length>index Then Return param[index]
-			Return Null
-		End
-		
-		'Internal message processing
-		Method OnMessage(msg:String,param:String[])
-			Local triggerOnMessage:Bool=False
-			Local container:IRCMessageContainer
-			Local fromUser:String
-			Local type:String
-			Local toUser:String
-			
-			fromUser=GetParam(0,param)
-			type=GetParam(1,param)
-			toUser=GetParam(2,param)
-			container=Self
-			
-			While msg.StartsWith(" ")
-				msg=msg.Slice(1)
-			Wend
-			
-			'For message information, visit: https://tools.ietf.org/html/rfc2812
-			
-			'Process message type
-			Select type.ToUpper()
-				Case "001" 'Welcome message
-					'Check if our nickname was accepted or not
-					If Self.nickname<>GetParam(2,param) Then
-						Self.nickname=GetParam(2,param)
-						parent.OnUserUpdate(container,Self)
-					Endif
-					
-				Case "376" 'End of MOTD
-					accepted=True
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					
-				Case "353" 'Username list
-					Local nameSplit:String[]
-					If msg.Contains(" ") Then 
-						nameSplit=msg.Split(" ")
-					Else
-						nameSplit=New String[1]
-						nameSplit[0]=msg
-					Endif
-					
-					container=GetMessageContainer(GetParam(4,param))
-					
-					'Is this the first time we're getting users?
-					If container then
-						If container.gotUsers Then
-							TriggerOnMessage(msg,fromUser,toUser,type,Self)
-						Else
-							container.gotUsers=True
-						Endif
-						
-						container.users.Clear()
-						
-						For Local i:Int=0 Until nameSplit.Length
-							Local nU:=New IRCUser
-							nU.parent=container
-							nU.name=nameSplit[i]
-							container.users.AddLast(nU)
-						Next
-						
-						container.SortUsers()
-					Else
-						TriggerOnMessage(msg,fromUser,toUser,type,Self)
-					Endif
-					
-				Case "366" 'End of username list
-					container=GetMessageContainer(GetParam(3,param))
-					parent.OnUserUpdate(container,Self)
-					
-				Case "331" 'Channel topic empty
-					container=GetMessageContainer(GetParam(3,param))
-					container.topic=""
-					
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					
-				Case "332" 'Channel topic
-					container=GetMessageContainer(GetParam(3,param))
-					container.topic=msg
-					'container.AddMessage("Topic for "+container.name+" is: "+msg,container.name,container.name,type,GetHostname(toUser))
-					
-					'parent.OnMessage(container.messages.Last,container,Self)
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					
-				Case "JOIN" 'Joining a new channel
-					Local chan:String=toUser
-					If Not chan Then chan=msg
-					container=GetMessageContainer(chan,ContainerType.Room)
-					
-					'Update message containers to contain new user
-					Local nU:=New IRCUser
-					nU.parent=container
-					nU.name=GetNickname(fromUser)
-					container.users.AddLast(nU)
-					parent.OnUserUpdate(container,Self)
-					
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					
-				Case "NOTICE"
-					'Notices are sent to all containers!
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					For container=Eachin Self.messageContainers
-						'If container.type=ContainerType.User Then Continue
-						TriggerOnMessage(msg,fromUser,toUser,type,container)
-					Next
-					
-				Case "PRIVMSG"
-					'Check if private messager from user or room
-					If toUser.StartsWith("#") Then
-						'Room
-						container=GetMessageContainer(toUser,ContainerType.Room)
-					Else
-						'Possibly from user!
-						If Not SkipContainer(GetNickname(fromUser)) Then 'skip some users
-							container=GetMessageContainer(GetNickname(fromUser),ContainerType.User)
-							If container.topic<>fromUser Then container.topic=fromUser
-						Endif
-					Endif
-					
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-					
-				Case "PART","QUIT" 'Leaving a channel
-					container=GetMessageContainer(toUser,ContainerType.Room)
-					
-					If GetNickname(fromUser)=nickname Then
-						RemoveMessageContainer(container)
-						container=Self
-					Else
-						For Local u:=Eachin container.users
-							If u.name=GetNickname(fromUser) Then
-								container.users.Remove(u)
-								TriggerOnMessage(msg,fromUser,toUser,type,container)
-								parent.OnUserUpdate(container,Self)
-								Exit
-							Endif
-						Next
-					Endif
-					
-				Case "NICK" 'Changing names
-					'Was local name?
-					Local wasSelf:Bool
-					If GetNickname(fromUser)=nickname Then
-						wasSelf=True
-						nickname=msg
-					Endif
-					
-					'Update message containers to new name
-					For container=Eachin Self.messageContainers
-						For Local u:=Eachin container.users
-							If u.name=GetNickname(fromUser) Then
-								u.name=msg
-								container.SortUsers()
-								
-								If Not wasSelf Then 
-									TriggerOnMessage(msg,fromUser,msg,type,container)
-									parent.OnUserUpdate(container,Self)
-								Endif
-								
-								Exit
-							Endif
-						Next
-					Next
-					
-					'Update server (No one in server but you!)
-					If wasSelf Then
-						container=Self
-						TriggerOnMessage(msg,fromUser,msg,type,container)
-						parent.OnUserUpdate(container,Self)
-					Endif
-					
-				Default
-					TriggerOnMessage(msg,fromUser,toUser,type,container)
-			End
-			
-		End
-		
-		'Internal message triggering method (sends to end user)
-		Method TriggerOnMessage(msg:String,fromUser:String,toUser:String,type:String,container:IRCMessageContainer)
-			If Not container Then container=Self
-			Local m:=New IRCMessage
-			m.text=msg
-			m.fromUser=GetNickname(fromUser)
-			m.toUser=toUser
-			m.type=type
-			m.hostname=fromUser
-			If parent Then parent.OnMessage(m,container,Self)
-		End
-	Public
-	
-	'Send string to server
-	Method SendString(str:String)
-		If Not str.EndsWith("~n") Then str+="~n"
-		Local sendBuffer:DataBuffer=New DataBuffer(str.Length)
-		sendBuffer.PokeString(0,str)
-		socket.Send(sendBuffer.Data,sendBuffer.Length)
-	End
-	
-	'Return a message container
-	'Option to create it if it doesn't exist
-	Method GetMessageContainer:IRCMessageContainer(name:String,addType:ContainerType=ContainerType.None)
-		If name="*" Or name=Self.name Or name.Length<=0 Then Return Self
-		
-		'First we look for existing containers
-		For Local c:=Eachin messageContainers
-			If c.name.ToLower()=name.ToLower() Then Return c
-		Next
-		
-		'Create a new container
-		If addType<>ContainerType.None Then Return AddMessageContainer(name,addType)
-		
-		Return Self
-	End
-	
-	'Add a new message container
-	Method AddMessageContainer:IRCMessageContainer(name:String,type:ContainerType)
-		Local nC:=New IRCMessageContainer
-		nC.parent=Self
-		nC.name=name
-		nC.type=type
-		messageContainers.AddLast(nC)
-		parent.OnNewContainer(nC,Self)
-		
-		'Load history for chat rooms
-		If nC.name.StartsWith("#") Then nC.LoadHistory()
-		
-		Return nC
-	End
-	
-	'Remove a specific message container
-	Method RemoveMessageContainer(container:IRCMessageContainer)
-		
-		'Save history for chat rooms
-		If container.name.StartsWith("#") Then container.SaveHistory()
-		
-		messageContainers.Remove(container)
-		If parent Then parent.OnRemoveContainer(container,Self)
-	End
-	
-	'Remove message container by name
-	Method RemoveMessageContainer(name:String)
-		For Local c:=Eachin messageContainers
-			If c.name.ToLower()=name.ToLower() Then
-					RemoveMessageContainer(c)
-				Return
-			Endif
-		Next
-	End
-	
-	'Strips hostname and returns nickname
-	Method GetNickname:String(str:String)
-		If str.Contains("!") Then Return str.Split("!")[0]
-		Return str
-	End
-	
-	'Strips nickname and return hostname
-	Method GetHostname:String(str:String)
-		If str.Contains("!") Then Return str.Split("!")[1]
-		Return str
-	End
-End
-
-'This class is used by servers to contain messages in rooms and private chats
-'It can also contain users and a topic which is mostly used for rooms
-Class IRCMessageContainer
-	Field parent:IRCServer
-	Field name:String
-	Field topic:String
-	Field type:ContainerType
-	Field users:=New List<IRCUser>
-	Field gotUsers:Bool 'Have we gotten users before?
-	Field messages:=New List<IRCMessage>
-	
-	Method LogPath:String()
-		
-		Return AppDir() + "/logs/" + parent.serverAddress + "/" + name + ".txt"
-		
-	End
-	
-	Method LoadHistory()
-		Local file:String=LoadString( Self.LogPath() )
-		If Not file Then Return
-		
-		Local lines:=file.Split( "~n" )
-		Local type:String
-		Local time:String
-		Local user:String
-		Local message:String
-		
-		For Local s:=Eachin lines
-			If Not s.Contains( ">" ) Or Not s.Contains( " " ) Or Not s.Contains( ":" ) Then Continue
-			
-			type=s.Split( ">" )[0]
-			time=s.Split( ">" )[1].Split( " " )[0]
-			user=s.Split( ">" )[1].Split( " " )[1].Split( ":" )[0]
-			message=s.Split( type + ">" + time + " " + user + ":" )[1]
-			
-			Self.AddMessage( message, user, Self.name, type.ToUpper() ).time=time
-		Next
-		
-	End
-	
-	Method SaveHistory()
-		Local log:String
-		
-		For Local m:=Eachin Self.messages
-			If m.type<>"PRIVMSG" And m.type<>"QUIT" And m.type<>"PART" And m.type<>"JOIN" And m.type<>"NICK" Then Continue
-			
-			log+=m.type+">"+m.time+" "+m.fromUser+":"+m.text+"~n"
-		Next
-		
-		If log.Length>2 Then
-			CreateFile( LogPath(), True )
-			SaveString( log, LogPath() )
-		Endif
-	End
-	
-	Method Remove() Virtual
-		parent.messageContainers.Remove(Self)
-	End
-	
-	Method GetUser:IRCUser(nick:String)
-		nick=nick.ToLower()
-		For Local u:=Eachin Self.users
-			If u.name.ToLower()=nick Then Return u
-		Next
-		Return Null
-	End
-	
-	'Add a message to this container
-	Method AddMessage:IRCMessage(msg:String,fromUser:String="",toUser:String="",type:String="",hostname:string="")
-		Local nM:=New IRCMessage
-		nM.parent=Self
-		nM.text=msg
-		nM.fromUser=fromUser
-		nM.toUser=toUser
-		nM.type=type
-		nM.hostname=hostname
-		messages.AddLast(nM)
-		Return nM
-	End
-	
-	'Sort the userlist
-	Method SortUsers()
-		Self.users.Sort(Lambda:Int(x:IRCUser,y:IRCUser)
-			Return CompareNames(x.name,y.name)
-		End)
-	End
-	
-	'Compare usernnames (for sorting)
-	Function CompareNames:Int(lhs:String,rhs:String)
-		if lhs.StartsWith("@") and rhs.StartsWith( "@" ) return lhs<=>rhs
-		if lhs.StartsWith("@") return -1
-		if rhs.StartsWith("@") return +1
-		if lhs.StartsWith("+") and rhs.StartsWith( "+" ) return lhs<=>rhs
-		if lhs.StartsWith("+") return -1
-		if rhs.StartsWith("+") return +1
-		Return lhs<=>rhs
-	End
-End
-
-'A single chat message
-'Used by message containers
-Class IRCMessage
-	Field parent:IRCMessageContainer
-	Field text:String
-	Field fromUser:String
-	Field toUser:String
-	Field type:String
-	Field hostname:String
-	Field time:String
-	
-	Function CurrentTime:String()
-		Local timePtr:=std.time.Time.Now()
-		Return timePtr.Hours+":"+timePtr.Minutes
-	End
-	
-	Method New()
-		Self.time=CurrentTime()
-	End
-End
-
-'A single user
-'Used by message containers
-Class IRCUser
-	Field parent:IRCMessageContainer
-	Field name:String
-End
-
-'=IRC VIEW=
-
-'Highlighter for IRC history text
-Function IrcTextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,state:Int )
-	Local i0:=sol
-	Local msgStep:Int
-	Local userColor:Int
-	Local userStart:Int
-	Local userEnd:Int
-	Local userDone:Bool
-	
-	While i0<eol
-		Local chr:=text[i0]
-		
-		If userDone Then 
-			colors[i0]=0
-		Else
-			colors[i0]=1
-		Endif
-		
-		'Reset
-		If chr=110 And msgStep=6 Then 'n
-			msgStep=0
-			userDone=False
-		Elseif msgStep=6 And chr<>110
-			msgStep=5
-		Endif
-		If chr=126 And msgStep=5 Then '~
-			msgStep=6
-		Endif
-		
-		'Detect username
-		If chr=9 And msgStep=2 Then 'Tab
-			userStart=i0
-			msgStep=3
-			userColor=0
-		Elseif msgStep=2 And chr<>9 Then
-			msgStep=5
-		Endif
-		If chr=32 And msgStep=4 Then 'Space after :
-			userEnd=i0-1
-			msgStep=5
-			userDone=True
-			For Local i1:Int=userStart Until userEnd
-				colors[i1]=2 + (userColor Mod 5)
-			Next
-		Elseif msgStep=4 And chr<>32 Then
-			msgStep=5
-		Endif
-		If chr=58 And msgStep=3 Then ':
-			msgStep=4
-		Endif
-		
-		If msgStep=3 Then userColor+=chr
-		
-		'Detect time
-		If chr=91 And msgStep=0 Then '[
-			msgStep=1
-		Endif
-		If msgStep=1 Then colors[i0]=1
-		
-		If chr=93 And msgStep=1 Then ']
-			msgStep=2
-		Endif
-		
-		i0+=1
-	Wend
-	
-	Return state
-End
-
-'This is a pre-made IRC client, ready to be used in any MojoX application
-Class IRCView Extends DockingView
-	Field ircHandler:IRC
-	
-	Field introScreen:IRCIntroView
-	Field chatScreen:DockingView
-	
-	Field topicField:TextFieldExt
-	Field historyField:TextView
-	Field bottomDocker:DockingView
-	Field inputField:TextFieldExt
-	Field nicknameLabel:Label
-	Field userList:ListView
-	Field serverTree:TreeView
-	Field selectedTreeNode:TreeView.Node
-	Field selectedServer:IRCServer
-	Field selectedMessageContainer:IRCMessageContainer
-	
-	Field maxHistory:Int=50
-	
-	Property Intro:IRCIntroView()
-		Return introScreen
-	End
-	
-	Method New()
-		Super.New()
-		
-		introScreen=New IRCIntroView(Self)
-		chatScreen=New DockingView
-		
-		'We move to chat screen by default and intro screen if it's used!
-		Self.ContentView=chatScreen
-		
-		'LAYOUT
-		
-		'Chat history field
-		historyField=New TextView
-		historyField.Document.TextHighlighter=IrcTextHighlighter
-		historyField.ReadOnly=True
-		historyField.WordWrap=True
-		chatScreen.ContentView=historyField
-		
-		'User list
-		userList=New ListView
-		chatScreen.AddView(userList,"right",Style.Font.TextWidth("arandomusername!"),True)
-		userList.ItemDoubleClicked+=Lambda(item:ListView.Item)
-			If selectedServer And selectedServer.accepted Then
-				selectedMessageContainer=selectedServer.AddMessageContainer(item.Text,ContainerType.User)
-				If selectedMessageContainer Then
-					UpdateServerTree()
-					serverTree.NodeClicked(GetMessageContainerNode(item.Text,selectedServer.name))
-				Endif
-			Endif
-		End
-		
-		'Server tree list
-		serverTree=New TreeView
-		chatScreen.AddView(serverTree,"left",Style.Font.TextWidth("irc.randomserver.com"),True)
-		
-		serverTree.NodeRightClicked+=Lambda(node:TreeView.Node)
-			serverTree.NodeClicked(node) 'Select node we right clicked
-			If Not selectedServer Or Not selectedMessageContainer Then Return
-			
-			Local menu:=New Menu
-			
-			'Is this a root node?
-			If node=serverTree.RootNode Then
-				'Nothing special
-			Else
-				'Is this is server node?
-				If selectedServer=selectedMessageContainer Then
-					
-					menu.AddAction("Add Channel").Triggered=Lambda()
-						New Fiber(Lambda()
-							Local chan:=RequestString("Enter channel name","Add new channel")
-							If chan Then
-								While chan.StartsWith("#") Or chan.StartsWith(" ")
-									chan=chan.Slice(1)
-								Wend
-								selectedServer.SendString("JOIN #"+chan)
-							Endif
-						End)
-					End
-					
-					menu.AddAction("Rename").Triggered=Lambda()
-						New Fiber(Lambda()
-							Local name:=RequestString("Enter new name","Change server name")
-							If name Then
-								'Does this name already exist?
-								Local exists:Bool
-								For Local s:=Eachin ircHandler.servers
-									If name.ToLower()=s.name.ToLower() Then
-										Notify("Name already exists","This name is already in use")
-										exists=True
-										Exit
-									Endif
-								Next
-								
-								If Not exists Then 
-									selectedServer.name=name
-									node.Text=name
-								Endif
-							Endif
-						End)
-					End
-					
-					If selectedServer.Connected Then
-						menu.AddAction("Disconnect").Triggered=Lambda()
-							selectedServer.Disconnect()
-						End
-					Else
-						menu.AddAction("Connect").Triggered=Lambda()
-							selectedServer.Connect()
-						End
-						
-						menu.AddAction("Remove").Triggered=Lambda()
-							selectedServer.Remove()
-							selectedMessageContainer=Null
-							selectedServer=Null
-							UpdateHistory()
-							node.Remove()
-							UpdateServerTree()
-						End
-					Endif
-					
-				Else
-					'Public room
-					If selectedMessageContainer.type=ContainerType.Room
-						menu.AddAction("Leave").Triggered=Lambda()
-							selectedServer.SendString("PART "+selectedMessageContainer.name)
-						End
-					Else 'Private room
-						menu.AddAction("Close").Triggered=Lambda()
-							selectedMessageContainer.Remove()
-							selectedMessageContainer=selectedServer
-							UpdateHistory()
-							node.Remove()
-						End
-					Endif
-				Endif
-			Endif
-			
-			'Always have the option to add a new server
-			menu.AddAction("Add Server").Triggered=Lambda()
-				New Fiber(Lambda()
-					Local serv:=RequestString("Server address","Add new server")
-					Local port:Int
-					Local nick:String
-					If serv Then
-						port=RequestInt("Server port","Add new server",6667)
-						If port Then
-							nick=RequestString("Nickname","Add new server")
-							If nick Then ircHandler.AddServer(nick,serv,serv,port)
-						Endif
-					Endif
-				End)
-			End
-			
-			menu.Open( )
-		End
-		
-		serverTree.NodeClicked+=Lambda(node:TreeView.Node)
-			If Not node Then Return
-			selectedTreeNode=node
-			
-			'Deselect everything except clicked node!
-			For Local s:=Eachin serverTree.RootNode.Children
-				s.Selected=False
-				For Local c:=Eachin s.Children 'Deselect kids too
-					c.Selected=False
-				Next
-			Next
-			
-			'Select clicked node
-			node.Selected=True
-			node.Icon=Null
-			
-			'Find the root server
-			If node.Parent=serverTree.RootNode Then
-				For Local s:=Eachin ircHandler.servers
-					If s.name=node.Text Then
-						selectedServer=s
-						Exit
-					Endif
-				Next
-			Endif
-			
-			'Find the actual message container we've clicked (can return the server itself!)
-			If selectedServer Then
-				selectedMessageContainer=selectedServer.GetMessageContainer(node.Text)
-			Endif
-			
-			UpdateHistory()
-			UpdateUsers()
-		End
-		
-		'Topic field
-		topicField=New TextFieldExt
-		topicField.BlockCursor=False
-		topicField.CursorType=CursorType.Line
-		topicField.ReadOnly=True
-		chatScreen.AddView(topicField,"top")
-		
-		'Bottom stuff
-		bottomDocker=New DockingView
-		chatScreen.AddView(bottomDocker,"bottom")
-		
-		'Nickname label
-		nicknameLabel=New Label()
-		bottomDocker.AddView(nicknameLabel,"left")
-		nicknameLabel.Clicked=Lambda()
-			If selectedServer And selectedServer.accepted Then
-				New Fiber(Lambda()
-					Local newNick:String=RequestString("Enter new nickname","Change nickname")
-					newNick=newNick.Trim()
-					If selectedServer.nickname<>newNick Then selectedServer.SendString("NICK "+newNick)
-				End)
-			Endif
-		End
-		
-		'User input field
-		inputField=New TextFieldExt
-		inputField.BlockCursor=False
-		inputField.CursorType=CursorType.Line
-		inputField.CursorBlinkRate=2.5
-		inputField.MaxLength=512
-		inputField.Entered+=Lambda()
-			SendInput(inputField.Text)
-			inputField.Text=Null
-		End
-		App.KeyEventFilter+=Lambda( event:KeyEvent )
-			If App.KeyView=inputField And event.Key=Key.KeypadEnter
-				SendInput(inputField.Text)
-				inputField.Text=Null
-			Endif
-		End
-		
-		
-		bottomDocker.ContentView=inputField
-		
-		'Setup IRC
-		ircHandler=New IRC
-		ircHandler.OnMessage=OnMessageIRC
-		ircHandler.OnUserUpdate=OnUserUpdateIRC
-		ircHandler.OnNewContainer=OnNewContainerIRC
-		ircHandler.OnRemoveContainer=OnRemoveContainerIRC
-	End
-	
-	'Add and connect to a new IRC server
-	'Nickname is your user name to connect with
-	'Name/Description of server, can by anything you want
-	'Server is IP/address
-	'Port is server... port...
-	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
-		Local nS:IRCServer
-		
-		If ircHandler Then
-			For Local s:=Eachin ircHandler.servers
-				If name.ToLower()=s.name.ToLower() Then
-					Notify("Name already exists","A server with this name already exists")
-					Return Null
-				Endif
-			Next
-			
-			nS=ircHandler.AddServer(nickname,name,server,port)
-		Endif
-		
-		Return nS
-	End
-	
-	Method SendInput(text:String)
-		If Not selectedMessageContainer Then Return
-		
-		text=text.Trim().Replace("~n","").Replace("~r","").Replace("~t","")
-		If text.Length<=0 Then Return
-		
-		'Make this a PRIVMSG automatically?
-		If text.StartsWith("/") Then
-			'Nope, send raw
-			text=text.Right(text.Length-1)
-			selectedMessageContainer.AddMessage(selectedServer.nickname+": "+text)
-		Else
-			'Yep, add PRIVMSG and send!
-			selectedMessageContainer.AddMessage( text, selectedServer.nickname, selectedMessageContainer.name, "PRIVMSG" )
-			text="PRIVMSG "+selectedMessageContainer.name+" :"+text
-		Endif
-		
-		ircHandler.servers.First.SendString(text)
-		AddChatMessage(selectedMessageContainer.messages.Last)
-	End
-	
-	Method SaveAllHistory()
-		For Local s:IRCServer=Eachin Self.ircHandler.servers
-		For Local c:IRCMessageContainer=Eachin s.messageContainers
-			c.SaveHistory()
-		Next
-		Next
-	End
-	
-	Method Quit(message:String=Null)
-		SaveAllHistory()
-		
-		For Local s:IRCServer=Eachin Self.ircHandler.servers
-			s.SendString("QUIT :"+message)
-			s.Disconnect()
-		Next
-	End
-	
-	Method OnMessageIRC(message:IRCMessage,container:IRCMessageContainer,server:IRCServer)
-		'For message information, visit: https://tools.ietf.org/html/rfc2812
-		Local doNotify:Bool 'Should we notify the user?
-		
-		Select message.type
-			Case "332" 'TOPIC
-				container.AddMessage("Topic for "+container.name+" is: "+message.text)
-				
-			Case "JOIN"
-				If message.fromUser=server.nickname Then
-					UpdateHistory()
-					container.AddMessage("You are now talking in "+container.name)
-				Else
-					container.AddMessage( message.text, message.fromUser, container.name, message.type)
-				Endif
-				
-			Case "PART"
-				container.AddMessage( message.text, message.fromUser, container.name, message.type)
-				
-			Case "QUIT"
-				container.AddMessage( message.text, message.fromUser, container.name, message.type)
-				
-			Case "NICK"
-				Local wasSelf:Bool
-				If container=server And selectedMessageContainer Then
-					wasSelf=true
-					container=selectedMessageContainer
-				Endif
-				
-				container.AddMessage( message.text, message.fromUser, message.toUser, message.type)
-				
-				If wasSelf Then UpdateUsers()
-				
-			Case "PRIVMSG"
-				container.AddMessage( message.text, message.fromUser, container.name, message.type)
-				doNotify=True
-			
-			Case "NOTICE"
-				container.AddMessage( message.text, message.fromUser, container.name, message.type)
-			
-			Default
-				container.AddMessage( message.text, message.fromUser, container.name, message.type)
-				doNotify=True
-		End
-		
-		'Display message if we're in that container right now
-		If container=selectedMessageContainer Then
-			AddChatMessage(container.messages.Last)
-		Elseif doNotify Then
-			Local node:TreeView.Node=GetMessageContainerNode(container.name,server.name)
-			If node Then node.Icon=App.Theme.OpenImage("irc/notice.png")
-		Endif
-	End
-	
-	Method OnUserUpdateIRC(container:IRCMessageContainer,server:IRCServer)
-		If container=selectedMessageContainer Then UpdateUsers()
-	End
-	
-	Method OnNewContainerIRC(container:IRCMessageContainer,server:IRCServer)
-		UpdateServerTree()
-		If container.type<>ContainerType.User Then
-			SelectMessageContainer(container.name,server.name)
-		Endif
-	End
-	
-	Method OnRemoveContainerIRC(container:IRCMessageContainer,server:IRCServer)
-		UpdateServerTree()
-		If container=selectedMessageContainer Then
-			selectedMessageContainer=Null
-			If container=server Then 
-				selectedServer=Null
-			Else
-				SelectMessageContainer(server.name)
-			Endif
-		Endif
-	End
-	
-	Method GetMessageContainerNode:TreeView.Node(serverName:String)
-		serverName=serverName.ToLower()
-		
-		For Local s:=Eachin serverTree.RootNode.Children
-			If s.Text.ToLower()=serverName Then Return s
-		Next
-		
-		Return Null
-	End
-	
-	Method GetMessageContainerNode:TreeView.Node(containerName:String,serverName:String)
-		serverName=serverName.ToLower()
-		containerName=containerName.ToLower()
-		
-		For Local s:=Eachin serverTree.RootNode.Children
-			If s.Text.ToLower()=serverName Then
-				For Local c:=Eachin s.Children
-					If c.Text.ToLower()=containerName Then Return c
-				Next
-			Endif
-		Next
-		
-		Return Null
-	End
-	
-	Method SelectMessageContainer(serverName:String)
-		Local foundNode:=GetMessageContainerNode(serverName)
-		If foundNode Then serverTree.NodeClicked(foundNode)
-	End
-	
-	Method SelectMessageContainer(containerName:String,serverName:String)
-		Local foundNode:=GetMessageContainerNode(containerName,serverName)
-		If foundNode Then serverTree.NodeClicked(foundNode)
-	End
-	
-	Method UpdateServerTree()
-		serverTree.RootNode.Text="No Server"
-		serverTree.RootNode.Expanded=True
-		
-		If ircHandler.servers.Count()>0 Then
-			serverTree.RootNodeVisible=False
-		Else
-			serverTree.RootNodeVisible=True
-			Return
-		Endif
-		
-		Local serverExists:Bool
-		Local channelExists:Bool
-		Local serverNode:TreeView.Node
-		Local channelNode:TreeView.Node
-		
-		'Scan for old removed nodes
-		For Local s:=Eachin serverTree.RootNode.Children
-			serverExists=False
-			
-			'Does this server still exist?
-			For Local is:=Eachin ircHandler.servers
-				If s.Text=is.name Then
-					serverExists=True
-					
-					'Does it channels exist?
-					For Local c:=Eachin s.Children
-						channelExists=False
-						
-						For Local ic:=Eachin is.messageContainers
-							If c.Text=ic.name Then
-								channelExists=True
-								Exit
-							Endif
-						Next
-						
-						If Not channelExists Then
-							'Remove the channel
-							c.Remove()
-						Endif
-					Next
-					
-					Exit
-				Endif
-			Next
-			
-			'Well, does it exist?
-			If Not serverExists Then
-				'NOPE! Remove it and all channels
-				s.Remove()
-			Endif
-		Next
-		
-		'Look for new servers and channels
-		For Local is:=Eachin ircHandler.servers
-			serverExists=False
-			
-			'Does this exist in the tree view already?
-			For Local s:=Eachin serverTree.RootNode.Children
-				If is.name=s.Text Then
-					serverNode=s
-					serverExists=True
-					Exit
-				Endif
-			Next
-			
-			'...Well?
-			If Not serverExists Then
-				'NOPE! Add it to the tree view
-				serverNode=New TreeView.Node(is.name,serverTree.RootNode)
-				serverNode.Expanded=True
-			Endif
-			
-			'Are all the channels there?
-			For Local ic:=Eachin is.messageContainers
-				channelExists=False
-				
-				For Local c:=Eachin serverNode.Children
-					If ic.name=c.Text Then
-						channelNode=c
-						channelExists=True
-						Exit
-					Endif
-				Next
-				
-				If Not channelExists Then
-					channelNode=New TreeView.Node(ic.name,serverNode)
-					serverNode.Expanded=True
-				Endif
-			Next
-		Next
-		
-		If Not selectedMessageContainer And serverTree.RootNode.Children.Length>0 Then
-			selectedTreeNode=serverTree.RootNode.Children[0]
-			serverTree.NodeClicked(selectedTreeNode)
-		Endif
-	End
-	
-	Method UpdateUsers()
-		userList.RemoveAllItems()
-		If Not selectedMessageContainer Then Return
-		For Local u:=Eachin selectedMessageContainer.users
-			userList.AddItem(u.name)
-		Next
-		
-		'Update own interface username
-		If selectedServer Then
-			nicknameLabel.Text=selectedServer.nickname+":"
-		Endif
-	End
-	
-	Method UpdateHistory()
-		historyField.Clear()
-		
-		If Not selectedMessageContainer Then Return
-		
-		'Limit message count
-		While selectedMessageContainer.messages.Count()>maxHistory
-			selectedMessageContainer.messages.Remove(selectedMessageContainer.messages.First)
-		Wend
-		
-		For Local m:=Eachin selectedMessageContainer.messages
-			AddChatMessage(m)
-		Next
-	End
-	
-	Function PadTime:String(timeStr:String)
-		If Not timeStr.Contains(":") Return timeStr
-		
-		Local t:=timeStr.Split(":")
-		If t[0].Length<=1 Then t[0]="0"+t[0]
-		If t[1].Length<=1 Then t[1]="0"+t[1]
-		
-		Return t[0]+":"+t[1]
-	End
-	
-	Method AddChatMessage(message:IRCMessage)
-		Local time:String="["+PadTime(message.time)+"]~t"
-		
-		Select message.type.ToUpper()
-			
-			Case "JOIN"
-				historyField.AppendText( time + message.fromUser + " joined " + message.toUser + "~n" )
-				
-			Case "PART"
-				If message.text Then
-					historyField.AppendText( time + message.fromUser + " left " + message.toUser + " (Reason " + message.text + ")~n" )
-				Else
-					historyField.AppendText( time + message.fromUser + " left " + message.toUser + "~n" )
-				Endif
-				
-			Case "QUIT"
-				If message.text Then
-					historyField.AppendText( time + message.fromUser + " quit (Reason '" + message.text + "')~n" )
-				Else
-					historyField.AppendText( time + message.fromUser + " quit~n" )
-				Endif
-				
-			Case "NICK"
-				historyField.AppendText( time + message.fromUser + " is now known as " + message.text + "~n" )
-				
-			Case "MODE"
-				'historyField.AppendText( time + message.fromUser + " sets MODE " + message.text + "~n" )
-				
-			Case "NOTICE"
-				historyField.AppendText( time + message.fromUser + ": <NOTICE> " + message.text + "~n" )
-				
-			Default
-				If message.fromUser Then
-					historyField.AppendText( time + message.fromUser + ": " + message.text + "~n" )
-				Else
-					historyField.AppendText( time + message.text + "~n" )
-				Endif
-		End
-		
-	End
-	
-	Method OnRender(canvas:Canvas) Override
-		Super.OnRender(canvas)
-		
-		If selectedMessageContainer Then
-			'Hide or show topic field or
-			If topicField.Text<>selectedMessageContainer.topic Then
-				topicField.Text=selectedMessageContainer.topic
-			Endif
-			' always hide topic to get more useful space
-			topicField.Visible=False '(topicField.Text.Length>0)
-			
-			'Hide or show user list
-			If selectedMessageContainer.users.Count()>0 Then
-				userList.Visible=True
-			Else
-				userList.Visible=False
-			Endif
-		Else
-			topicField.Visible=False
-			userList.Visible=False
-		Endif
-	End
-End
-
-Class IRCIntroView Extends DockingView
-	Field parent:IRCView
-	Field text:String
-	Field introLabel:Label
-	Field servers:=New List<IRCServer>
-	Field roomScroller:ScrollableView
-	Field nickBox:DockingView
-	Field nickField:TextFieldExt
-	Field nickLabel:Label
-	Field checkboxes:=New List<CheckButton>
-	Field connectButton:Button
-	
-	Field OnConnect:Void()
-	Field OnNickChange:Void(nick:String)
-	
-	Property Text:String()
-		Return text
-	Setter(str:String)
-		text=str
-	End
-	
-	Property IsConnected:Bool()
-		Return _connected
-	End
-	
-	Method New(owner:IRCView)
-		Super.New()
-		Self.parent=owner
-	End
-	
-	Method AddOnlyServer:IRCServer( nickname:String,name:String,server:String,port:Int,rooms:String )
-		
-		servers.Clear()
-		
-		Local nS:IRCServer
-		
-		For Local s:=Eachin servers
-			If name.ToLower()=s.name.ToLower() Then
-				Notify("Name already exists","A server with this name already exists")
-				Return Null
-			Endif
-		Next
-		
-		nS=New IRCServer(nickname,name,server,port,False)
-		nS.AutoJoinRooms+=rooms
-		
-		servers.AddLast(nS)
-		UpdateInterface()
-		parent.ContentView=Self
-		Return nS
-	End
-	
-	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
-		Local nS:IRCServer
-	
-		For Local s:=Eachin servers
-			If name.ToLower()=s.name.ToLower() Then
-				Notify("Name already exists","A server with this name already exists")
-				Return Null
-			Endif
-		Next
-		
-		nS=New IRCServer(nickname,name,server,port,False)
-		
-		servers.AddLast(nS)
-		UpdateInterface()
-		parent.ContentView=Self
-		Return nS
-	End
-	
-	Method UpdateInterface()
-		
-		Self.RemoveAllViews()
-		
-		introLabel=New Label(text)
-		introLabel.TextGravity=New Vec2f(0.5,0.5)
-		Self.AddView(introLabel,"top")
-		
-		nickBox=New DockingView
-		nickBox.Layout="fill-y"
-		Self.AddView(nickBox,"top")
-		
-		nickLabel=New Label("Nickname")
-		nickBox.AddView(nickLabel,"left")
-		
-		nickField=New TextFieldExt
-		nickField.BlockCursor=False
-		nickField.TextChanged+=Lambda()
-			OnNickChange(nickField.Text)
-		End
-		nickBox.ContentView=nickField
-		
-		roomScroller=New ScrollableView
-		roomScroller.Layout="fill-y"
-		Self.ContentView=roomScroller
-		
-		For Local s:=Eachin Self.servers
-			If Not nickField.Text And s.nickname Then nickField.Text=s.nickname
-			Local chans:=s.AutoJoinRooms.Split("#")
-			For Local c:=Eachin chans
-				If Not c Then Continue
-				checkboxes.AddLast(New CheckButton(s.name+" - #"+c))
-				checkboxes.Last.Checked=True
-				roomScroller.AddView(checkboxes.Last,"top")
-			Next
-		Next
-		
-		connectButton=New Button("Connect")
-		roomScroller.AddView(connectButton,"top")
-		connectButton.Clicked=Lambda()
-			Connect()
-		End
-	End
-	
-	Method Connect()
-		
-		'Is nickname set?
-		If Not nickField.Text Notify( "","Please, enter your nickname." ) ; Return
-		
-		'Local server:=AddServer( _nick,_server,_server,_port )
-		'If server Then server.AutoJoinRooms+=_rooms
-		
-		Local serv:String
-		Local chan:String
-		Local didHaveChans:Bool
-		
-		'Reset all auto join  strings
-		For Local s:=Eachin servers
-			s.AutoJoinRooms=Null
-		Next
-		
-		'Loop throught all checkboxes and get the server and channel 
-		For Local c:=Eachin Self.checkboxes
-			If Not c.Checked Then Continue 'Skip boxes that aren't checked
-			serv=c.Text.Split(" - ")[0].ToLower()
-			chan=c.Text.Split(" - ")[1].ToLower()
-			For Local s:=Eachin servers
-				If serv=s.name.ToLower() Then
-					didHaveChans=True
-					s.AutoJoinRooms+=chan
-				Endif
-			Next
-		Next
-		
-		'Did we have ANY channels to join?
-		If Not didHaveChans Then Return
-		
-		'Send all servers
-		For Local s:=Eachin servers
-			If Not s.AutoJoinRooms Then Continue
-			Local serv:=parent.AddServer(nickField.Text,s.name,s.serverAddress,s.serverPort)
-			serv.AutoJoinRooms=s.AutoJoinRooms
-		Next
-		
-		'Set the parent IRC view to display the chat screen now
-		parent.ContentView=parent.chatScreen
-		
-		_connected=True
-		OnConnect()
-	End
-	
-	
-	Private
-	
-	Field _connected:Bool
-	
-End
-
-#rem
-'=EXAMPLE=
-Class MyWindow Extends Window
-	Field ircView:IRCView
-	
-	Method New(title:String="IRC Test",width:Int=1280*0.85,height:Int=720*0.85,flags:WindowFlags=WindowFlags.Resizable)
-		Super.New(title,width,height,flags)
-		
-		'Create our IRC view
-		ircView=New IRCView
-		ContentView=ircView
-		
-		'Add a new server and connect to it
-		Local nick:="M2_TestUser"
-		Local desc:="freenode"
-		Local server:="irc.freenode.net"'"irc.du.se"
-		Local port:=6667
-		Local serv:IRCServer
-		
-		'Add a server to our intro scren
-		serv=ircView.introScreen.AddServer(nick,desc,server,port)
-		
-		'Select title for our intro screen
-		ircView.introScreen.Text="koreIRC Example"
-		
-		'You can skip the intro screen by adding the server directly to the IRC view
-		'serv=ircView.AddServer(nick,desc,server,port)
-		
-		'Add rooms to connect to at start
-		If serv Then
-			serv.AutoJoinRooms+="#monkey2#heztest"
-			'serv.AutoJoinRooms+="#heztest"
-			
-			'You'll want to update the intro interface after adding rooms
-			ircView.introScreen.UpdateInterface()
-		Endif
-	End
-	
-	Method OnRender( canvas:Canvas ) Override
-		App.RequestRender()
-	End
-End
-#end

+ 37 - 1
view/ListViewExt.monkey2

@@ -43,6 +43,7 @@ End
 Class ListViewExt Extends ScrollableView
 
 	Field OnItemChoosen:Void()
+	Field OnItemChoosenDblClick:Void()
 	
 	Method New()
 		Self.New( 20,50 )
@@ -70,6 +71,14 @@ Class ListViewExt Extends ScrollableView
 		_visibleCount=Min( _maxLines,_items.Length )
 	End
 	
+	Method RemoveItem( item:ListViewItem )
+	
+		_items.Remove( item )
+	
+		_visibleCount=Min( _maxLines,_items.Length )
+		_selIndex=Clamp( _selIndex,0,_items.Length-1 )
+	End
+	
 	Method SetItems<T>( items:Stack<T> ) Where T Extends ListViewItem
 		
 		_items.Clear()
@@ -82,6 +91,11 @@ Class ListViewExt Extends ScrollableView
 		AddItems( items )
 	End
 	
+	Method Sort<T>( func:Int(l:T,r:T) ) Where T Extends ListViewItem
+	
+		_items.Sort( func )
+	End
+	
 	Method Reset()
 		
 		_selIndex=0
@@ -110,6 +124,11 @@ Class ListViewExt Extends ScrollableView
 		Return _items[_selIndex]
 	End
 	
+	Property CurrentIndex:Int()
+	
+		Return _selIndex
+	End
+	
 	Property MoveCyclic:Bool()
 		Return _moveCyclic
 	Setter( value:Bool )
@@ -140,6 +159,15 @@ Class ListViewExt Extends ScrollableView
 		RequestRender()
 	End
 	
+	Method SelectByIndex( index:Int )
+		
+		Assert( _selIndex >= 0 And _selIndex < _items.Length,"Index out of bounds!" )
+		
+		_selIndex=index
+		EnsureVisible()
+		RequestRender()
+	End
+	
 	Method PageUp()
 	
 		_selIndex-=_visibleCount
@@ -159,9 +187,15 @@ Class ListViewExt Extends ScrollableView
 	End
 	
 	Property Items:Stack<ListViewItem>.Iterator()
+	
 		Return _items.All()
 	End
 	
+	Property Empty:Bool()
+		
+		Return _items.Empty
+	End
+	
 	Method DrawItem( item:ListViewItem,canvas:Canvas,x:Float,y:Float,handleX:Float=0,handleY:Float=0 ) Virtual
 	
 		item.Draw( canvas,x,y,handleX,handleY )
@@ -298,7 +332,7 @@ Class ListViewExt Extends ScrollableView
 			
 		Case EventType.MouseMove
 			
-			'If VisibleRect.Contains(MouseLocation) Then RequestRender()
+			'If VisibleRect.Contains( MouseLocation ) Then RequestRender()
 		
 		Case EventType.MouseDoubleClick
 		
@@ -308,6 +342,7 @@ Class ListViewExt Extends ScrollableView
 			
 			_selIndex=index
 			OnItemChoosen()
+			OnItemChoosenDblClick()
 			
 		Case EventType.MouseClick
 		
@@ -317,6 +352,7 @@ Class ListViewExt Extends ScrollableView
 			
 			_selIndex=index
 			OnItemChoosen()
+			RequestRender()
 			
 		Default
 			Return

+ 97 - 4
view/ProjectBrowserView.monkey2

@@ -41,8 +41,9 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 		
 		New Fiber( Lambda()
 			
+			Local point:=eventLocation-Scroll
 			Local node:=Cast<Node>( item )
-			Local node2:=FindNodeAtPoint( eventLocation )
+			Local node2:=FindNodeAtPoint( point )
 			If Not node2 Return
 			
 			Local destNode:=Cast<Node>( node2 )
@@ -120,11 +121,46 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 	Method OnDragStarted()
 		
 		_draggingState=True
+		
+		' timer for dragging stuff: scroll & expand
+		'
+		If _draggingAutoscrollTimer Then _draggingAutoscrollTimer.Cancel()
+		_draggingAutoscrollTimer=New Timer( 15,Lambda()
+			
+			If _draggingAutoscrollValue
+				
+				' autoscroll
+				'
+				Local sc:=Scroll
+				sc.y+=_draggingAutoscrollValue
+				Scroll=sc
+			
+			Else
+				
+				' autoexpand
+				'
+				Local point:=MainWindow.TransformPointToView( App.MouseLocation,Self )
+				Local node:=FindNodeAtPoint( point )
+				If node And Not node.Expanded And node.Children
+					If node<>_draggingNodeToExpand
+						_draggingNodeToExpand=node
+						_draggingExpandCounter=0
+					Endif
+					_draggingExpandCounter+=1
+					If _draggingExpandCounter>=15
+						node.Expanded=True
+						OnNodeExpanded( node )
+					Endif
+				Endif
+			Endif
+			
+		End )
 	End
 	
 	Method OnDragEnded()
 		
 		_draggingState=False
+		If _draggingAutoscrollTimer Then _draggingAutoscrollTimer.Cancel()
 	End
 	
 	Method OnFileDropped:Bool( path:String )
@@ -225,6 +261,18 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 		If node Then Refresh( node )
 	End
 	
+	Method CollapseAll( node:TreeView.Node )
+		
+		For Local child:=Eachin node.Children
+			CollapseAll( child )
+		Next
+		
+		If node.Expanded
+			node.Expanded=False
+			OnCollapsed( node,False )
+		Endif
+	End
+	
 	
 	Protected
 	
@@ -332,6 +380,10 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 	Field _draggableView:Button
 	Field _draggingState:Bool
 	Field _draggingText:String
+	Field _draggingAutoscrollValue:=0
+	Field _draggingAutoscrollTimer:Timer
+	Field _draggingNodeToExpand:TreeView.Node
+	Field _draggingExpandCounter:=0
 	
 	Global _listener:DraggableProjTreeListener
 	
@@ -354,14 +406,16 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 	
 	Method OnDraggedInto( node:Node,name:String )
 		
-		Local path:=node.Text+"\"+name
+		Local path:=GetNodePath( node )+"\"+name
 		
 		node.Expanded=True
 		_expander.Store( node )
 		Local par:=IsProjectNode( node ) ? node Else node.Parent
 		OnNodeExpanded( par ) 'update parent folder
 		
-		SelectByPathEnds( path )
+		MainWindow.UpdateWindow( False )
+		
+		SelectByPath( path )
 		
 	End
 	
@@ -369,6 +423,21 @@ Class ProjectBrowserView Extends TreeViewExt Implements IDraggableHolder
 		
 		If _draggingState
 			_draggableView.Text=(Keyboard.Modifiers & Modifier.Control = 0) ? _draggingText Else _draggingText+" (copy)"
+			
+			' autoscrolling area in dragging state
+			'
+			Local y:=MainWindow.TransformPointToView( App.MouseLocation,Self ).y
+			Local h:=Frame.Height
+			Local dy:=35*App.Theme.Scale.x
+			Local val:=20*App.Theme.Scale.x
+			
+			If y<dy
+				_draggingAutoscrollValue=-val
+			Elseif y>h-dy
+				_draggingAutoscrollValue=val
+			Else
+				_draggingAutoscrollValue=0
+			Endif
 		Endif
 	End
 	
@@ -665,7 +734,12 @@ Class DraggableProjTreeListener Extends DraggableViewListener<ProjectBrowserView
 		
 		Local projTree:=FindViewInHierarchy<ProjectBrowserView>( eventView )
 		
-		Return Cast<ProjectBrowserView.Node>( projTree?.FindNodeAtPoint( eventLocation ) )
+		If projTree
+			Local point:=eventLocation-projTree.Scroll
+			Return Cast<ProjectBrowserView.Node>( projTree?.FindNodeAtPoint( point ) )
+		Endif
+		
+		Return Null
 	End
 	
 	Method GetHolder:ProjectBrowserView( view:View ) Override
@@ -674,3 +748,22 @@ Class DraggableProjTreeListener Extends DraggableViewListener<ProjectBrowserView
 	End
 	
 End
+
+
+Class View Extension
+	
+	#Rem monkeydoc Return this view or nearest parent view with a given type of T.
+	#End
+	Method FindView<T>:T( checkSelf:Bool=False ) Where T Extends View
+		
+		Local view:=checkSelf ? Self Else Self.Parent
+		While view
+			Local res:=Cast<T>( view )
+			If res Return res
+			view=view.Parent
+		Wend
+		
+		Return null
+	End
+	
+End

+ 19 - 17
view/ProjectView.monkey2

@@ -2,7 +2,7 @@
 Namespace ted2go
 
 
-Class ProjectView Extends ScrollView
+Class ProjectView Extends DockingView
 
 	Field openProject:Action
 	
@@ -20,8 +20,6 @@ Class ProjectView Extends ScrollView
 		
 		ContentView=_docker
 		
-		_docker.ContentView=New TreeViewExt
-		
 		openProject=New Action( "Open project" )
 		openProject.HotKey=Key.O
 		openProject.HotKeyModifiers=Modifier.Menu|Modifier.Shift
@@ -30,6 +28,11 @@ Class ProjectView Extends ScrollView
 		InitProjBrowser()
 	End
 	
+	Property SelectedItem:ProjectBrowserView.Node()
+	
+		Return Cast<ProjectBrowserView.Node>( _projBrowser.Selected )
+	End
+	
 	Property OpenProjects:String[]()
 	
 		Return _projects.ToArray()
@@ -136,19 +139,6 @@ Class ProjectView Extends ScrollView
 	
 	Protected
 	
-	Method OnMouseEvent( event:MouseEvent ) Override
-	
-		Select event.Type
-		Case EventType.MouseWheel ' little faster
-			
-			Scroll-=New Vec2i( 0,ContentView.RenderStyle.Font.Height*event.Wheel.Y*3 )
-			Return
-	
-		End
-	
-		Super.OnMouseEvent( event )
-	End
-	
 	
 	Private
 	
@@ -337,7 +327,7 @@ Class ProjectView Extends ScrollView
 		Local browser:=New ProjectBrowserView()
 		browser.SingleClickExpanding=Prefs.MainProjectSingleClickExpanding
 		_projBrowser=browser
-		_docker.AddView( browser,"top" )
+		_docker.ContentView=browser
 		
 		browser.RequestedDelete+=Lambda( node:ProjectBrowserView.Node )
 		
@@ -601,6 +591,18 @@ Class ProjectView Extends ScrollView
 			End
 			pasteAction.Enabled=(_cutPath Or _copyPath) And isFolder
 			
+			' collapse all
+			'
+			If isFolder
+				
+				menu.AddSeparator()
+				
+				menu.AddAction( "Collapse all" ).Triggered=Lambda()
+				
+					_projBrowser.CollapseAll( node )
+				End
+			Endif
+				
 			menu.Open()
 		End
 		

+ 1916 - 0
view/TextViewExt.monkey2

@@ -0,0 +1,1916 @@
+
+Namespace ted2go
+
+
+#rem monkeydoc The TextDocument class.
+#end
+Class TextDocument
+	
+	#rem monkeydoc Invoked after text has changed.
+	#end
+	Field TextChanged:Void()
+	
+	#rem monkeydoc Invoked after lines have been modified.
+	#end
+	Field LinesModified:Void( first:Int,removed:Int,inserted:Int )
+	
+	#rem monkeydoc Creates a new text document.
+	#end
+	Method New()
+		
+		_lines.Push( New Line )
+	End
+
+	#rem monkeydoc Document text.
+	#end
+	Property Text:String()
+		
+		Return _text
+		
+	Setter( text:String )
+		
+		text=text.Replace( "~r~n","~n" )
+		text=text.Replace( "~r","~n" )
+		
+		ReplaceText( 0,_text.Length,text )
+	End
+	
+	#rem monkeydoc Length of doucment text.
+	#end
+	Property TextLength:Int()
+		
+		Return _text.Length
+	End
+	
+	#rem monkeydoc Number of lines in document.
+	#end
+	Property NumLines:Int()
+		
+		Return _lines.Length
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Property Colors:Byte[]()
+		
+		Return _colors.Data
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property TextHighlighter:TextHighlighter()
+		
+		Return _highlighter
+		
+	Setter( textHighlighter:TextHighlighter )
+		
+		_highlighter=textHighlighter
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method LineState:Int( line:Int )
+		
+		If line>=0 And line<_lines.Length Return _lines[line].state
+		Return -1
+	End
+	
+	#rem monkeydoc Gets the index of the first character on a line.
+	#end
+	Method StartOfLine:Int( line:Int )
+		
+		If line<=0 Return 0
+		If line<_lines.Length Return _lines[line-1].eol+1
+		Return _text.Length
+	End
+	
+	#rem monkeydoc Gets the index of the last character on a line.
+	#end
+	Method EndOfLine:Int( line:Int )
+		
+		If line<0 Return 0
+		If line<_lines.Length Return _lines[line].eol
+		Return _text.Length
+	End
+	
+	#rem monkeydoc Finds the line containing a character.
+	#end
+	Method FindLine:Int( index:Int )
+		
+		If index<=0 Return 0
+		If index>=_text.Length Return _lines.Length-1
+		
+		Local min:=0,max:=_lines.Length-1
+		
+		Repeat
+			Local line:=(min+max)/2
+			If index>_lines[line].eol
+				min=line+1
+			Else If max-min<2
+				Return min
+			Else
+				max=line
+			Endif
+		Forever
+		
+		Return 0
+	End
+
+	#rem monkeydoc Gets line text.
+	#end
+	Method GetLine:String( line:Int )
+		
+		Return _text.Slice( StartOfLine( line ),EndOfLine( line ) )
+	End
+	
+	#Rem monkeydoc hidden
+	#End
+'	Method SetLineVisible( line:Int,visible:Bool )
+'		
+'		_hiddens[line]=Not visible
+'	End
+
+	#rem monkeydoc Appends text to the end of the document.
+	#end
+	Method AppendText( text:String )
+		
+		ReplaceText( _text.Length,_text.Length,text )
+	End
+	
+	#rem monkeydoc Replaces  text in the document.
+	#end
+	Method ReplaceText( anchor:Int,cursor:Int,text:String )
+		
+		Local min:=Min( anchor,cursor )
+		Local max:=Max( anchor,cursor )
+		
+		Local eols1:=0,eols2:=0
+		For Local i:=min Until max
+			If _text[i]=10 eols1+=1
+		Next
+		For Local i:=0 Until text.Length
+			If text[i]=10 eols2+=1
+		Next
+		
+		Local dlines:=eols2-eols1
+		Local dchars:=text.Length-(max-min)
+		
+		Local line0:=FindLine( anchor )
+		Local line:=FindLine( min )
+		Local eol:=StartOfLine( line )-1
+		
+		'Print "eols1="+eols1+", eols2="+eols2+", dlines="+dlines+", dchars="+dchars+" text="+text.Length
+		
+		'Move data!
+		'
+		Local oldlen:=_text.Length
+		_text=_text.Slice( 0,min )+text+_text.Slice( max )
+		
+		_colors.Resize( _text.Length )
+		Local p:=_colors.Data.Data
+		libc.memmove( p + min + text.Length, p + max , oldlen-max )
+		libc.memset( p + min , 0 , text.Length )
+		
+		'Update lines
+		'
+		If dlines>=0
+			
+			If dlines>0
+				_lines.Resize( _lines.Length+dlines )
+				'_hiddens.Resize( _lines.Length )
+			Endif
+			
+			Local i:=_lines.Length
+			While i>line+eols2+1
+				i-=1
+				_lines.Data[i].eol=_lines[i-dlines].eol+dchars
+				_lines.Data[i].state=_lines[i-dlines].state
+				'_hiddens.Data[i]=_hiddens.Data[i-dlines]
+			Wend
+			
+		Endif
+		
+		For Local i:=0 Until eols2+1
+			eol=_text.Find( "~n",eol+1 )
+			If eol=-1 eol=_text.Length
+			_lines.Data[line+i].eol=eol
+			_lines.Data[line+i].state=-1
+		Next
+		
+		If dlines<0
+			
+			Local i:=line+eols2+1
+			While i<_lines.Length+dlines
+				_lines.Data[i].eol=_lines[i-dlines].eol+dchars
+				_lines.Data[i].state=_lines[i-dlines].state
+				i+=1
+			Wend
+			
+			_lines.Resize( _lines.Length+dlines )
+			'_hiddens.Resize( _lines.Length )
+		Endif
+		
+		If _highlighter<>Null
+			
+			'update highlighting
+			'
+			Local state:=-1
+			If line state=_lines[line-1].state
+			
+			For Local i:=0 Until eols2+1
+				state=_highlighter( _text,_colors.Data,StartOfLine( line ),EndOfLine( line ),state )
+				_lines.Data[line].state=state
+				line+=1
+			Next
+			
+			While line<_lines.Length 'And state<>_lines[line].state
+				state=_highlighter( _text,_colors.Data,StartOfLine( line ),EndOfLine( line ),state )
+				_lines.Data[line].state=state
+				line+=1
+			End
+		Endif
+		
+		LinesModified( line0,eols1,eols2 )
+		
+		TextChanged()
+	End
+	
+	Private
+	
+	Struct Line
+		Field eol:Int
+		Field state:Int
+	End
+	
+	Field _text:String
+	
+	Field _lines:=New Stack<Line>
+	Field _colors:=New Stack<Byte>
+	'Field _hiddens:=New Stack<Bool>
+	Field _highlighter:TextHighlighter
+	
+End
+
+
+#rem monkeydoc The TextView class.
+#end
+Class TextView Extends ScrollableView
+	
+	#rem monkeydoc Invoked when cursor moves.
+	#end
+	Field CursorMoved:Void()
+	
+	#rem monkeydoc Creates a new text view.
+	#end
+	Method New()
+		
+		Style=GetStyle( "TextView" )
+		ContentView.Style=GetStyle( "TextViewContent" )
+		
+		_lines.Push( New Line )
+		
+		UpdateColors()
+		
+		Document=New TextDocument
+	End
+
+	Method New( text:String )
+		
+		Self.New()
+		
+		Document.Text=text
+	End
+	
+	Method New( doc:TextDocument )
+		
+		Self.New()
+		
+		Document=doc
+	End
+
+	#rem monkeydoc Text document.
+	#end
+	Property Document:TextDocument()
+		
+		Return _doc
+		
+	Setter( doc:TextDocument )
+		
+		If _doc _doc.LinesModified-=LinesModified
+		
+		_doc=doc
+		
+		_doc.LinesModified+=LinesModified
+		
+		_cursor=Clamp( _cursor,0,_doc.TextLength )
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	Public
+	
+	#rem monkeydoc Text colors.
+	#end
+	Property TextColors:Color[]()
+		
+		Return _textColors
+		
+	Setter( textColors:Color[] )
+		
+		_textColors=textColors
+	End
+	
+	#rem monkeydoc Cursor color.
+	#end
+	Property CursorColor:Color()
+		
+		Return _cursorColor
+		
+	Setter( cursorColor:Color )
+		
+		_cursorColor=cursorColor
+	End
+	
+	#rem monkeydoc Selection color.
+	#end
+	Property SelectionColor:Color()
+		
+		Return _selColor
+		
+	Setter( selectionColor:Color )
+		
+		_selColor=selectionColor
+	End
+
+	#rem monkeydoc Cursor type.
+	#end
+	Property CursorType:CursorType()
+		
+		Return _cursorType
+		
+	Setter( type:CursorType )
+		
+		_cursorType=type
+	End
+
+	#rem monkeydoc @deprecated Use [[CursorType]].
+	#end
+	Property BlockCursor:Bool()
+		
+		Return _cursorType=CursorType.Block
+		
+	Setter( block:Bool )
+		
+		_cursorType=block ? CursorType.Block Else CursorType.Line
+	End
+	
+	#rem monkeydoc Cursor blink rate.
+	
+	Set to 0 for non-blinking cursor.
+	
+	#end
+	Property CursorBlinkRate:Float()
+		
+		Return _blinkRate
+		
+	Setter( blinkRate:Float )
+		
+		_blinkRate=blinkRate
+		
+		RequestRender()
+	End
+
+	#rem monkeydoc Text.
+	#end
+	Property Text:String()
+		
+		Return _doc.Text
+		
+	Setter( text:String )
+		
+		_doc.Text=text
+	End
+	
+	#rem monkeydoc Read only flag.
+	#end
+	Property ReadOnly:Bool()
+		
+		Return _readOnly
+		
+	Setter( readOnly:Bool )
+		
+		_readOnly=readOnly
+	End
+	
+	#rem monkeydoc Tabstop.
+	#end
+	Property TabStop:Int()
+		
+		Return _tabStop
+		
+	Setter( tabStop:Int )
+		
+		_tabStop=tabStop
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc WordWrap flag.
+	#end
+	Property WordWrap:Bool()
+		
+		Return _wordWrap
+		
+	Setter( wordWrap:Bool )
+		
+		_wordWrap=wordWrap
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc Cursor character index.
+	#end
+	Property Cursor:Int()
+		
+		Return _cursor
+	End
+	
+	#rem monkeydoc Anchor character index.
+	#end
+	Property Anchor:Int()
+		
+		Return _anchor
+	End
+	
+	#rem monkeydoc Line the cursor is on.
+	#end
+	Property CursorLine:Int()
+		
+		Return _doc.FindLine( _cursor )
+	End
+	
+	#rem monkeydoc Cursor rect.
+	#end
+	Property CursorRect:Recti()
+		
+		Return _cursorRect
+	End
+	
+	#rem monkeydoc Approximate character width.
+	#end
+	Property CharWidth:Int()
+		
+		Return _charw
+	End
+	
+	#rem monkeydoc Approximate character height.
+	#end
+	Property CharHeight:Int()
+		
+		Return _charh
+	End
+	
+	#rem monkeydoc Approximate line height.
+	
+	Deprecated! Use [[LineRect]] instead to properly deal with word wrap.
+	
+	#end
+	Property LineHeight:Int()
+		
+		Return _charh
+	End
+	
+	#rem monkeydoc Line spacing koefficien. Default is 1.0.
+	#end
+	Property LineSpacing:Float()
+	
+		Return _lineSpacing
+		
+	Setter( value:Float )
+		
+		If value=_lineSpacing Return
+		
+		_lineSpacing=value
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc True if undo available.
+	#end
+	Property CanUndo:Bool()
+		
+		Return Not _readOnly And Not _undos.Empty
+	End
+	
+	#rem monkeydoc True if redo available.
+	#end
+	Property CanRedo:Bool()
+		
+		Return Not _readOnly And Not _redos.Empty
+	End
+	
+	#rem monkeydoc True if cut available.
+	#end
+	Property CanCut:Bool()
+		
+		Return Not _readOnly And _anchor<>_cursor
+	End
+	
+	#rem monkeydoc True if copy available.
+	#end
+	Property CanCopy:Bool()
+		
+		Return _anchor<>_cursor
+	End
+	
+	#rem monkeydoc True if paste available.
+	#end
+	Property CanPaste:Bool()
+		
+		Return Not _readOnly And Not App.ClipboardTextEmpty
+	End
+	
+	#rem monkeydoc Returns the rect containing a character at a given index.
+	#end
+	Method CharRect:Recti( index:Int )
+		
+		Local line:=_doc.FindLine( index )
+		
+		Local text:=_doc.Text
+		
+		Local i0:=_doc.StartOfLine( line )
+		Local eol:=_doc.EndOfLine( line )
+		
+		Local x0:=0,y0:=_lines[line].rect.Top
+		
+		While i0<eol
+			
+			Local w:=WordWidth( text,i0,eol,x0 )
+			
+			If x0+w>_wrapw
+				y0+=_charh
+				x0=0
+			Endif
+			
+			Local l:=WordLength( text,i0,eol )
+			
+			If index<i0+l
+				x0+=WordWidth( text,i0,index,x0 )
+				i0=index
+				Exit
+			Endif
+			
+			x0+=w
+			i0+=l
+			
+		Wend
+		
+		Local w:=_charw
+		If i0<eol And text[i0]>32 w=_font.TextWidth( text.Slice( i0,i0+1 ) )
+		
+		Return New Recti( x0,y0,x0+w,y0+_charh/_lineSpacing )
+	End
+	
+	#rem monkeydoc Returns the index of the character nearest to a given point.
+	#end
+	Method CharAtPoint:Int( p:Vec2i )
+		
+		Local line:=LineAtPoint( p )
+		Local text:=_doc.Text
+		
+		Local i0:=_doc.StartOfLine( line )
+		Local eol:=_doc.EndOfLine( line )
+		
+		Local x0:=0,y0:=_lines[line].rect.Top+_charh
+		
+		While i0<eol
+			
+			Local w:=WordWidth( text,i0,eol,x0 )
+			
+			If x0+w>_wrapw
+				If p.y<y0 Exit
+				y0+=_charh
+				x0=0
+			Endif
+			
+			Local l:=WordLength( text,i0,eol )
+			
+			If p.x<x0+w And p.y<y0
+				For Local i:=0 Until l
+					x0+=WordWidth( text,i0,i0+1,x0 )
+					If p.x<x0 Exit
+					i0+=1
+				Next
+				Exit
+			Endif
+			
+			x0+=w
+			i0+=l
+			
+		Wend
+		
+		Return i0
+	
+	End
+	
+	#rem monkedoc Returns the index of the line nearest to a given point.
+	#end
+	Method LineAtPoint:Int( p:Vec2i )
+		
+		Local y:=p.y
+		If y<=0 Return 0
+		If y>=_lines.Top.rect.Top Return _lines.Length-1
+		
+		Local min:=0,max:=_lines.Length-1
+		
+		Repeat
+			Local line:=(min+max)/2
+			If y>=_lines[line].rect.Bottom
+				min=line+1
+			Else If max-min>1
+				max=line
+			Else
+				Return min
+			Endif
+		Forever
+		
+		Return 0
+	End
+
+	#rem monkeydoc Gets the bounding rect for a line.
+	#end
+	Method LineRect:Recti( line:Int )
+		
+		If line>=0 And line<_lines.Length
+			Return _lines[line].rect
+		Endif
+		
+		Return New Recti
+	End
+	
+	#Rem monkeydoc hidden
+	#End
+	Method SetLineVisible( line:Int,visible:Bool )
+		
+		Local L:=_lines[line]
+		L.visible=visible
+		_lines[line]=L
+	End
+	
+	#Rem monkeydoc hidden
+	#End
+	Method UpdateLineWidth( line:Int )
+		
+		Local L:=_lines[line]
+		Local size:=L.rect.Size
+		size.x=MeasureLine( line ).x
+		L.rect.Size=size
+		'L.rect=rect
+		_lines[line]=L
+	End
+	
+	#Rem monkeydoc hidden
+	#End
+	Method IsLineVisible:Bool( line:Int )
+		
+		Return _lines[line].visible
+	End
+	
+	#rem monkeydoc Clears all text.
+	#end
+	Method Clear()
+		
+		SelectAll()
+		ReplaceText( "" )
+	End
+	
+	#rem monkeydoc Move cursor to line.
+	#end
+	Method GotoLine( line:Int )
+		
+		_anchor=_doc.StartOfLine( line )
+		_cursor=_anchor
+		UpdateCursor()
+	End
+	
+	#rem monkeydoc Selects a line.
+	#end
+	Method SelectLine( line:Int )
+		
+		SelectText( _doc.StartOfLine( line ),_doc.EndOfLine( line ) )
+	End
+
+	#rem monkeydoc Selects text in a range.
+	#end
+	Method SelectText( anchor:Int,cursor:Int )
+		
+		_anchor=Clamp( anchor,0,_doc.TextLength )
+		_cursor=Clamp( cursor,0,_doc.TextLength )
+		
+		UpdateCursor()
+	End
+	
+	#rem monkeydoc Appends text.
+	#end
+	Method AppendText( text:String )
+		
+		SelectText( _doc.TextLength,_doc.TextLength )
+		ReplaceText( text )
+	End
+	
+	#rem monkeydoc Replaces current selection.
+	#end
+	Method ReplaceText( text:String )
+		
+		Local undo:=New UndoOp
+		undo.text=_doc.Text.Slice( Min( _anchor,_cursor ),Max( _anchor,_cursor ) )
+		undo.anchor=Min( _anchor,_cursor )
+		undo.cursor=undo.anchor+text.Length
+		_undos.Push( undo )
+		
+		ReplaceText( _anchor,_cursor,text )
+	End
+	
+	'non-undoable
+	#rem monkeydoc @hidden
+	#end
+	Method ReplaceText( anchor:Int,cursor:Int,text:String )
+		
+		_redos.Clear()
+	
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=Min( anchor,cursor )+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	#rem monkeydoc Performs an undo.
+	#end
+	Method Undo()
+		
+		If _readOnly Return
+	
+		If _undos.Empty Return
+		
+		Local undo:=_undos.Pop()
+		
+		Local text:=undo.text
+		Local anchor:=undo.anchor
+		Local cursor:=undo.cursor
+		
+		undo.text=_doc.Text.Slice( anchor,cursor )
+		undo.cursor=anchor+text.Length
+		
+		_redos.Push( undo )
+		
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=anchor+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	#rem monkeydoc Performs a redo.
+	#end
+	Method Redo()
+		
+		If _readOnly Return
+		
+		If _redos.Empty Return
+		
+		Local undo:=_redos.Pop()
+		
+		Local text:=undo.text
+		Local anchor:=undo.anchor
+		Local cursor:=undo.cursor
+		
+		undo.text=_doc.Text.Slice( anchor,cursor )
+		undo.cursor=anchor+text.Length
+		
+		_undos.Push( undo )
+		
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=anchor+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	#rem monkeydoc Selects all text.
+	#end
+	Method SelectAll()
+		
+		SelectText( 0,_doc.TextLength )
+	End
+	
+	#rem monkeydoc Performs a cut.
+	#end
+	Method Cut()
+		
+		If _readOnly Return
+		Copy()
+		ReplaceText( "" )
+	End
+	
+	#rem monkeydoc Performs a copy.
+	#end
+	Method Copy()
+		
+		Local min:=Min( _anchor,_cursor )
+		Local max:=Max( _anchor,_cursor )
+		Local text:=_doc.Text.Slice( min,max )
+		App.ClipboardText=text
+	End
+	
+	#rem monkeydoc Performs a paste.
+	#end
+	Method Paste()
+		
+		If _readOnly Return
+		
+		If App.ClipboardTextEmpty Return
+		
+		Local text:String=App.ClipboardText
+		text=text.Replace( "~r~n","~n" )
+		text=text.Replace( "~r","~n" )
+		
+		If text ReplaceText( text )
+	End
+	
+	Struct Word
+		
+		Field index:Int
+		Field length:Int
+		field rect:Recti
+		
+		Method New( index:Int,length:Int,rect:Recti )
+			Self.index=index
+			Self.length=length
+			Self.rect=rect
+		End
+		
+		Property Index:Int()
+			Return index
+		End
+		
+		Property Length:Int()
+			Return length
+		End
+		
+		Property Rect:Recti()
+			Return rect
+		End
+		
+	End
+	
+	Class WordIterator
+		
+		Method New( view:TextView )
+			Init( view,0,view.Text.Length )
+		End
+		
+		Property AtEnd:Bool()
+			Return _i0>=_eol
+		End
+		
+		Property Current:Word()
+			Return New Word( _i0,_l,_r )
+		End
+		
+		Method Bump()
+			_x0+=_w
+			_i0+=_l
+			
+			If _i0>=_eol _w=0 ; _l=0 ; Return
+			
+			_w=_view.WordWidth( _view.Text,_i0,_eol,_x0 )
+			_l=_view.WordLength( _view.Text,_i0,_eol )
+			
+			If _x0+_w>_view._wrapw
+				_y0+=_view._charh
+				_x0=0
+			Endif
+			
+			_r=New Recti( _x0,_y0,_x0+_w,_y0+_h )
+		End
+		
+		Function ForLine:WordIterator( view:TextView,line:Int )
+			Local i0:=view._doc.StartOfLine( line )
+			Local eol:=view._doc.EndOfLine( line )
+			Return New WordIterator( view,i0,eol )
+		End
+		
+		Private
+		
+		Field _view:TextView
+		Field _line:Int
+		
+		Field _i0:Int
+		Field _eol:Int
+		Field _x0:Int
+		Field _y0:Int
+		Field _w:Int
+		Field _h:Int
+		Field _l:Int
+		Field _r:Recti
+		
+		Method New( view:TextView,i0:Int,eol:Int )
+			
+			Init( view,i0,eol )
+		End
+		
+		Method Init( view:TextView,i0:Int,eol:Int )
+			
+			_view=view
+			
+			_i0=i0
+			_eol=eol
+			
+			_x0=0
+			_y0=_view.CharRect( i0 ).Top
+			_h=_view._charh
+			
+			If _i0>=_eol Return
+			
+			_w=_view.WordWidth( _view.Text,_i0,_eol,_x0 )
+			_l=_view.WordLength( _view.Text,_i0,_eol )
+			
+			_r=New Recti( _x0,_y0,_x0+_w,_y0+_h )
+		End
+		
+	End
+	
+	#rem monkeydoc @hidden
+	#End
+	Method Words:WordIterator()
+		
+		Return New WordIterator( Self )
+	End
+	
+	Protected
+	
+	Method OnThemeChanged() Override
+	
+		UpdateColors()
+	End
+	
+	Method OnValidateStyle() Override
+		
+		Local style:=RenderStyle
+		
+		_font=style.Font
+		
+		_charw=_font.TextWidth( "X" )
+		_charh=_font.Height*_lineSpacing
+		
+		_tabw=_charw*_tabStop
+		
+		UpdateLines()
+	End
+	
+	Method OnMeasureContent:Vec2i() Override
+		
+		If _wordWrap Return New Vec2i( 0,0 )
+		
+		If _wrapw<>$7fffffff
+			_wrapw=$7fffffff
+			UpdateLines()
+		Endif
+		
+		Return New Vec2i( _lines.Top.maxWidth+_charw,_lines.Top.rect.Bottom )
+	End
+
+	Method OnMeasureContent2:Vec2i( size:Vec2i ) Override
+		
+		If Not _wordWrap Return New Vec2i( 0,0 )
+		
+		If _wrapw<>size.x
+			_wrapw=size.x
+			UpdateLines()
+		Endif
+		
+		Return New Vec2i( _lines.Top.maxWidth,_lines.Top.rect.Bottom )
+	End
+	
+	Method OnRenderContent( canvas:Canvas ) Override
+		
+		OnRenderContent( canvas,VisibleRect )
+	End
+	
+	Method OnRenderContent( canvas:Canvas,clip:Recti ) Virtual
+		
+		If App.KeyView=Self And Not _blinkTimer RestartBlinkTimer()
+		
+		Local firstLine:=LineAtPoint( New Vec2i( 0,clip.Top ) )
+		Local lastLine:=LineAtPoint( New Vec2i( 0,clip.Bottom-1 ) )+1
+		
+		If _cursor<>_anchor
+		
+			Local min:=CharRect( Min( _anchor,_cursor ) )
+			Local max:=CharRect( Max( _anchor,_cursor ) )
+			
+			canvas.Color=_selColor
+			
+			If min.Y=max.Y
+				canvas.DrawRect( min.Left,min.Top,max.Left-min.Left,min.Height )
+			Else
+				canvas.DrawRect( min.Left,min.Top,(clip.Right-min.Left),min.Height )
+				canvas.DrawRect( 0,min.Bottom,clip.Right,max.Top-min.Bottom )
+				canvas.DrawRect( 0,max.Top,max.Left,max.Height )
+			Endif
+			
+		Endif
+		
+		If Not _readOnly And App.KeyView=Self And _blinkOn
+			
+			canvas.Color=_cursorColor
+			
+			Select _cursorType
+			Case CursorType.Block
+				canvas.DrawRect( _cursorRect )
+			Case CursorType.IBeam
+				canvas.DrawRect( _cursorRect.X,_cursorRect.Y,1,_cursorRect.Height )
+				canvas.DrawRect( _cursorRect.X-2,_cursorRect.Y,5,1 )
+				canvas.DrawRect( _cursorRect.X-2,_cursorRect.Y+_cursorRect.Height-1,5,1 )
+			Default
+				canvas.DrawRect( Max( _cursorRect.X-1,0 ),_cursorRect.Y,2,_cursorRect.Height )
+			End
+			
+		Endif
+		
+		_textColors[0]=RenderStyle.TextColor
+		
+		For Local line:=firstLine Until lastLine
+			
+			If _lines[line].visible
+				OnRenderLine( canvas,line )
+			Endif
+		Next
+	End
+	
+	Method OnRenderLine( canvas:Canvas,line:Int ) Virtual
+		
+		Local text:=_doc.Text
+		Local colors:=_doc.Colors
+		
+		For local word:=Eachin WordIterator.ForLine( Self,line )
+			
+			If text[word.Index]<=32 Continue
+			
+			Local i0:=word.Index
+			
+			Local i1:=i0+word.Length
+			
+			Local x0:=word.Rect.Left,y0:=word.Rect.Top
+			
+			While i0<i1
+			
+				Local start:=i0
+				Local color:=colors[start]
+				i0+=1
+			
+				While i0<i1 And colors[i0]=color
+					i0+=1
+				Wend
+				
+				If color<0 Or color>=_textColors.Length color=0
+				
+				canvas.Color=_textColors[color]
+				
+				Local str:=text.Slice( start,i0 )
+				
+				canvas.DrawText( str,x0,y0 )
+				
+				x0+=_font.TextWidth( str )
+			Wend
+			
+		Next
+		
+	End
+	
+	Method OnKeyDown:Bool( key:Key,modifiers:Modifier ) Virtual
+		
+		Select key
+		Case Key.Backspace
+			
+			If _anchor=_cursor And _cursor>0 SelectText( _cursor,_cursor-1 )
+			ReplaceText( "" )
+			
+		Case Key.KeyDelete
+			
+			If _anchor=_cursor And _cursor<_doc.Text.Length SelectText( _cursor,_cursor+1 )
+			ReplaceText( "" )
+			
+		Case Key.Tab
+			
+			Local cmin:=Min( _cursor,_anchor )
+			Local cmax:=Max( _cursor,_anchor )
+			
+			Local min:=_doc.FindLine( cmin )
+			Local max:=_doc.FindLine( cmax )
+			
+			If min=max
+				ReplaceText( "~t" )
+			Else
+				'select all lines...
+				cmin=_doc.StartOfLine( min )
+				If _doc.StartOfLine( max )<>cmax
+					max+=1
+					cmax=_doc.StartOfLine( max )
+				Endif
+				SelectText( cmin,cmax )
+				
+				Local lines:=New StringStack
+				
+				For Local i:=min Until max
+					lines.Push( _doc.GetLine( i ) )
+				Next
+				
+				Local go:=True
+				
+				If modifiers & Modifier.Shift
+					
+					For Local i:=0 Until lines.Length
+						
+						If Not lines[i].Trim()
+							lines[i]+="~n"
+							Continue
+						Endif
+						
+						If lines[i][0]=9
+							lines[i]=lines[i].Slice( 1 )+"~n"
+							Continue
+						Endif
+						
+						go=False
+						Exit
+					Next
+				Else
+					
+					For Local i:=0 Until lines.Length
+						lines[i]="~t"+lines[i]+"~n"
+					Next
+				Endif
+				
+				If go
+					ReplaceText( lines.Join( "" ) )
+					SelectText( _doc.StartOfLine( min ),_doc.StartOfLine( max ) )
+				Endif
+				
+			Endif
+			
+		Case Key.Enter,Key.KeypadEnter
+			
+			ReplaceText( "~n" )
+			
+			'auto indent!
+			Local line:=CursorLine
+			If line>0
+				
+				Local ptext:=_doc.GetLine( line-1 )
+				
+				Local indent:=ptext
+				For Local i:=0 Until ptext.Length
+					If ptext[i]<=32 Continue
+					indent=ptext.Slice( 0,i )
+					Exit
+				Next
+				
+				If indent ReplaceText( indent )
+				
+			Endif
+			
+		Case Key.Left
+			
+			If _anchor<>_cursor And Not (modifiers & Modifier.Shift)
+				_cursor=Min( _anchor,_cursor )
+			Else If _cursor
+				Repeat
+					_cursor-=1
+					Local line:=_doc.FindLine( _cursor )
+					If Not _lines[line].visible
+						line-=1
+						If line<0
+							_cursor=0
+							Exit
+						Else
+							_cursor=_doc.EndOfLine( line )+1
+						Endif
+					Else
+						Exit
+					Endif
+				Forever
+			Endif
+			UpdateCursor()
+			Return True
+			
+		Case Key.Right
+			
+			If _anchor<>_cursor And Not (modifiers & Modifier.Shift)
+				_cursor=Max( _anchor,_cursor )
+			Else If _cursor<_doc.Text.Length
+				Repeat
+					_cursor+=1
+					Local line:=_doc.FindLine( _cursor )
+					If Not _lines[line].visible
+						line+=1
+						If line>=_lines.Length
+							_cursor=Text.Length
+							Exit
+						Else
+							_cursor=_doc.EndOfLine( line )-1
+						Endif
+					Else
+						Exit
+					Endif
+				Forever
+			Endif
+			UpdateCursor()
+			Return True
+			
+		Case Key.Home
+			
+			_cursor=_doc.StartOfLine( CursorLine )
+			UpdateCursor()
+			Return True
+			
+		Case Key.KeyEnd
+			
+			_cursor=_doc.EndOfLine( CursorLine )
+			UpdateCursor()
+			Return True
+			
+		Case Key.Up
+			
+			MoveLine( -1 )
+			Return True
+			
+		Case Key.Down
+			
+			MoveLine( 1 )
+			Return True
+			
+		Case Key.PageUp
+			
+			Local n:=VisibleRect.Height/_charh-1		'shouldn't really use cliprect here...
+			MoveLine( -n )
+			Return True
+			
+		Case Key.PageDown
+			
+			Local n:=VisibleRect.Height/_charh-1
+			MoveLine( n )
+			Return True
+			
+		End
+		
+		Return False
+	End
+	
+	Method OnControlKeyDown:Bool( key:Key,modifiers:Modifier ) Virtual
+		
+		Select key
+		Case Key.A
+			SelectAll()
+		Case Key.X
+			Cut()
+		Case Key.C
+			Copy()
+		Case Key.V
+			Paste()
+		Case Key.Z
+			Undo()
+		Case Key.Y
+			Redo()
+		Case Key.Home
+			_cursor=0
+			UpdateCursor()
+			Return True
+		Case Key.KeyEnd
+			_cursor=_doc.TextLength
+			UpdateCursor()
+			Return True
+		Case Key.Left
+			If _anchor<>_cursor And Not (modifiers & Modifier.Shift)
+				_cursor=Min( _anchor,_cursor )
+			Endif
+			
+			Local text:=Text
+			Local term:=New Int[1]
+			_cursor=FindWord( _cursor,term )
+			
+			While _cursor And text[_cursor-1]<=32 And text[_cursor-1]<>10
+				_cursor-=1
+			Wend
+			
+			_cursor=FindWord( Max( _cursor-1,0 ),term )
+			_cursor+=1
+			Repeat ' skip invisible lines
+				_cursor-=1
+				Local line:=_doc.FindLine( _cursor )
+				If Not _lines[line].visible
+					line-=1
+					If line<0
+						_cursor=0
+						Exit
+					Else
+						_cursor=_doc.EndOfLine( line )+1
+					Endif
+				Else
+					Exit
+				Endif
+			Forever
+			
+			UpdateCursor()
+			Return True
+		Case Key.Right
+			If _anchor<>_cursor And Not (modifiers & Modifier.Shift)
+				_cursor=Max( _anchor,_cursor )
+			Endif
+			'next word...
+			Local text:=Text
+			Local term:=New Int[1]
+			FindWord( _cursor,term )
+			_cursor=term[0]
+			While _cursor<text.Length And text[_cursor]<=32 And text[_cursor]<>10
+				_cursor+=1
+			Wend
+			_cursor-=1
+			Repeat ' skip invisible lines
+				_cursor+=1
+				Local line:=_doc.FindLine( _cursor )
+				If Not _lines[line].visible
+					line+=1
+					If line>=_lines.Length
+						_cursor=Text.Length
+						Exit
+					Else
+						_cursor=_doc.EndOfLine( line )-1
+					Endif
+				Else
+					Exit
+				Endif
+			Forever
+			
+			UpdateCursor()
+			Return True
+		End
+		
+		Return False
+	End
+	
+	Method OnKeyChar( text:String ) Virtual
+		
+		If _undos.Length
+			
+			Local undo:=_undos.Top
+			If Not undo.text And _cursor=undo.cursor
+				ReplaceText( _anchor,_cursor,text )
+				undo.cursor=_cursor
+				Return
+			Endif
+			
+		Endif
+		
+		ReplaceText( text )
+	End
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+		
+		If _readOnly
+			If _macosMode
+				If (event.Modifiers & Modifier.Gui) And (event.Key=Key.C Or event.Key=Key.A)
+					'Copy, Select All
+				Else
+					Return
+				Endif
+			Else
+				If (event.Modifiers & Modifier.Control) And (event.Key=Key.C Or event.Key=Key.A)
+					'Copy, Select All
+				Else
+					Return
+				Endif
+			Endif
+		Endif
+	
+		Select event.Type
+		
+		Case EventType.KeyDown,EventType.KeyRepeat
+		
+			Local key:=event.Key
+			Local modifiers:=event.Modifiers
+			
+			'Note: NumLock doesn't work here on any of my keyboards on macos. I get both keypad consts
+			'AND '0', '1', KeyChars, and NumLock modifier is always 'off' so ignore Keypad consts
+			'on macos for now.
+			'
+#If __TARGET__<>"macos"
+			'map keypad nav keys...
+			If Not (modifiers & Modifier.NumLock)
+				Select key
+				Case Key.Keypad1 key=Key.KeyEnd
+				Case Key.Keypad2 key=Key.Down
+				Case Key.Keypad3 key=Key.PageDown
+				Case Key.Keypad4 key=Key.Left
+				Case Key.Keypad6 key=Key.Right
+				Case Key.Keypad7 key=Key.Home
+				Case Key.Keypad8 key=Key.Up
+				Case Key.Keypad9 key=Key.PageUp
+				Case Key.Keypad0 key=Key.Insert
+				End
+			Endif
+#endif
+			
+			Local r:=False
+			
+			If _macosMode
+			
+				If modifiers & Modifier.Gui
+				
+					Select key
+					Case Key.A,Key.X,Key.C,Key.V,Key.Z,Key.Y,Key.Left,Key.Right
+						r=OnControlKeyDown( key,modifiers )
+					End
+				
+				Else If modifiers & Modifier.Control
+				
+					Select key
+					Case Key.A
+						r=OnKeyDown( Key.Home,modifiers )
+					Case Key.E
+						r=OnKeyDown( Key.KeyEnd,modifiers )
+					End
+					
+				Else
+					
+					Select key
+					Case Key.Home,Key.KeyEnd
+						r=OnControlKeyDown( key,modifiers )
+					Default
+						r=OnKeyDown( key,modifiers )
+					End
+					
+				Endif
+				
+			Else
+				
+				If modifiers & Modifier.Control
+					r=OnControlKeyDown( key,modifiers )
+				Else
+					r=OnKeyDown( key,modifiers )
+				Endif
+				
+			Endif
+			
+			If r And Not (modifiers & Modifier.Shift) _anchor=_cursor
+			
+		Case EventType.KeyChar
+			
+			OnKeyChar( event.Text )
+			
+		End
+	End
+	
+	Method OnContentMouseEvent( event:MouseEvent ) Override
+		
+		Select event.Type
+		Case EventType.MouseDown
+			
+			Select event.Clicks
+			Case 1
+				
+				_cursor=CharAtPoint( event.Location )
+				
+				If Not (event.Modifiers & Modifier.Shift) _anchor=_cursor
+				
+				_dragging=True
+				
+				MakeKeyView()
+				
+				UpdateCursor()
+			
+			Case 2
+				
+				Local term:=New Int[1]
+				Local start:=FindWord( CharAtPoint( event.Location ),term )
+				
+				SelectText( start,term[0] )
+				
+			Case 3
+				
+				SelectLine( LineAtPoint( event.Location ) )
+				
+			End
+			
+			Return
+			
+		Case EventType.MouseUp
+			
+			_dragging=False
+			
+		Case EventType.MouseMove
+			
+			If _dragging
+				
+				_cursor=CharAtPoint( event.Location )
+				
+				UpdateCursor()
+				
+			Endif
+			
+		Case EventType.MouseWheel
+			
+			Return
+		End
+		
+		event.Eat()
+	End
+	
+	Private
+	
+	Struct Line
+		Field rect:Recti
+		Field maxWidth:Int
+		Field visible:=True
+	End
+	
+	Class UndoOp
+		Field text:String
+		Field anchor:Int
+		Field cursor:Int
+	End
+	
+	
+	Field _doc:TextDocument
+	Field _lines:=New Stack<Line>
+	Field _hidden:=New IntMap<Line>
+	Field _tabStop:Int=4
+	Field _cursorColor:Color=New Color( 0,.5,1,1 )
+	Field _selColor:Color=New Color( 1,1,1,.25 )
+	Field _cursorType:CursorType
+	Field _blinkRate:Float=0
+	Field _blinkOn:Bool=True
+	Field _blinkTimer:Timer
+	
+#if __HOSTOS__="macos"
+	Field _macosMode:Bool=True
+#else
+	Field _macosMode:Bool=False
+#endif
+	
+	Field _textColors:Color[]
+	
+	Field _anchor:Int
+	Field _cursor:Int
+	
+	Field _font:Font
+	Field _charw:Int
+	Field _charh:Int
+	Field _tabw:Int
+	Field _lineSpacing:=1.0
+	
+	Field _wordWrap:Bool=False
+	Field _wrapw:Int=$7fffffff
+	
+	Field _cursorRect:Recti
+	Field _vcursor:Vec2i
+	
+	Field _undos:=New Stack<UndoOp>
+	Field _redos:=New Stack<UndoOp>
+	
+	Field _dragging:Bool
+	
+	Field _readOnly:Bool
+
+	Method UpdateColors()
+		
+		CursorColor=App.Theme.GetColor( "textview-cursor" )
+		
+		SelectionColor=App.Theme.GetColor( "textview-selection" )
+		
+		Local colors:=New Color[8]
+		
+		For Local i:=0 Until 8
+			colors[i]=App.Theme.GetColor( "textview-color"+i )
+		Next
+		
+		TextColors=colors
+	End
+	
+	Method CancelBlinkTimer()
+		
+		If Not _blinkTimer Return
+		_blinkTimer.Cancel()
+		_blinkTimer=Null
+		_blinkOn=True
+	End
+	
+	Method RestartBlinkTimer()
+		
+		CancelBlinkTimer()
+		If Not _blinkRate Or App.KeyView<>Self Return
+		_blinkTimer=New Timer( _blinkRate,Lambda()
+			If App.KeyView<>Self Or Not _blinkRate
+				CancelBlinkTimer()
+				Return
+			Endif
+			_blinkOn=Not _blinkOn
+			RequestRender()
+		End )
+	End
+	
+	Method UpdateCursor()
+		
+		Local rect:=CharRect( _cursor )
+		
+		EnsureVisible( rect )
+		
+		_vcursor=rect.Origin
+		
+		If rect<>_cursorRect
+			
+			RestartBlinkTimer()
+			
+			_cursorRect=rect
+			
+			CursorMoved()
+		Endif
+		
+		RequestRender()
+	End
+	
+	Method FindWord:Int( from:Int,term:Int[] )
+		
+		Local text:=Text
+		
+		If from<0
+			term[0]=0
+			Return 0
+		Else If from>=text.Length
+			term[0]=text.Length
+			Return text.Length
+		Else If text[from]=10
+			term[0]=from+1
+			Return from
+		Endif
+		
+		Local start:=from,ends:=from+1
+		
+		If text[from]<=32
+			While start And text[start-1]<=32 And text[start-1]<>10
+				start-=1
+			Wend
+			While ends<text.Length And text[ends]<=32 And text[ends]<>10
+				ends+=1
+			Wend
+		Else if IsIdent( text[start] )
+			While start And IsIdent( text[start-1] )
+				start-=1
+			Wend
+			While ends<text.Length And IsIdent( text[ends] )
+				ends+=1
+			Wend
+		Else
+			While start And text[start-1]>32 And Not IsIdent( text[start-1] )
+				start-=1
+			Wend
+			While ends<text.Length And text[ends]>32 And Not IsIdent( text[ends] )
+				ends+=1
+			Wend
+		Endif
+		
+		term[0]=ends
+		Return start
+	End
+	
+	Method WordLength:Int( text:String,i0:Int,eol:Int )
+		
+		Local i1:=i0
+		
+		If text[i1]<=32
+			While i1<eol And text[i1]<=32
+				i1+=1
+			Wend
+		Else If IsIdent( text[i1] )
+			While i1<eol And IsIdent( text[i1] )
+				i1+=1
+			Wend
+		Else
+			While i1<eol And text[i1]>32 And Not IsIdent( text[i1] )
+				i1+=1
+			Wend
+		Endif
+		
+		Return i1-i0
+	End
+	
+	Method WordWidth:Int( text:String,i0:Int,eol:Int,x0:Int )
+		
+		Local i1:=i0,x1:=x0
+		'Print eol+" "+text.Length+text
+		If text[i0]<=32
+			While i1<eol And text[i1]<=32
+				If text[i1]=9
+					x1=Int( (x1+_tabw)/_tabw ) * _tabw
+				Else
+					x1+=_charw
+				Endif
+				i1+=1
+			Wend
+		Else
+			If IsIdent( text[i1] )
+				While i1<eol And IsIdent( text[i1] )
+					i1+=1
+				Wend
+			Else
+				While i1<eol And text[i1]>32 And Not IsIdent( text[i1] )
+					i1+=1
+				Wend
+			Endif
+			x1+=_font.TextWidth( text.Slice( i0,i1 ) )
+		Endif
+		
+		Return x1-x0
+	End
+	
+	Method MeasureLine:Vec2i( line:Int )
+		
+		Local text:=_doc.Text
+		Local i0:=_doc.StartOfLine( line )
+		Local eol:=_doc.EndOfLine( line )
+		
+		Local x0:=0,y0:=_charh,maxw:=0
+		
+		While i0<eol
+			
+			Local w:=WordWidth( text,i0,eol,x0 )
+			
+			If x0+w>_wrapw	'-_charw
+				maxw=Max( maxw,x0 )
+				y0+=_charh
+				x0=0
+			Endif
+			x0+=w
+			
+			i0+=WordLength( text,i0,eol )
+		Wend
+		
+		maxw=Max( maxw,x0 )
+		
+		Return New Vec2i( maxw,y0 )
+	End
+	
+	Method MoveLine( delta:Int )
+		
+		Local vcursor:=_vcursor
+		
+		_vcursor.y+=delta * _charh
+		
+		_cursor=CharAtPoint( _vcursor )
+		
+		UpdateCursor()
+		
+		_vcursor.x=vcursor.x
+	End
+	
+	Method UpdateLines()
+		
+		Local liney:=0,maxWidth:=0
+		Local rect:Recti
+		
+		For Local i:=0 Until _lines.Length
+			
+			Local line:=_lines[i]
+			
+			If Not line.visible
+				
+				line.rect=rect
+				
+			Else
+				
+				Local size:=MeasureLine( i )
+				
+				maxWidth=Max( maxWidth,size.x )
+				rect=New Recti( 0,liney,size.x,liney+size.y )
+				
+				line.maxWidth=maxWidth
+				line.rect=rect
+				
+				liney+=size.y
+				
+			Endif
+			
+			_lines[i]=line
+			
+		Next
+		
+		UpdateCursor()
+	End
+	
+	Method LinesModified( first:Int,removed:Int,inserted:Int )
+		
+'		Print "Lines modified: first="+first+", removed="+removed+", inserted="+inserted+", _charh="+_charh
+		
+		ValidateStyle()
+		
+		Local last:=first+inserted+1
+		
+		Local dlines:=inserted-removed
+		
+		If dlines>0
+			
+			_lines.Resize( _lines.Length+dlines )
+			
+			Local i:=_lines.Length
+			While i>last
+				i-=1
+				_lines[i]=_lines[i-dlines]
+			Wend
+			
+		Endif
+		
+		Local liney:=0,maxWidth:=0
+		If first
+			liney=_lines[first-1].rect.Bottom
+			maxWidth=_lines[first-1].maxWidth
+		Endif
+		
+		Local rect:Recti
+		
+		For Local i:=first Until last
+			
+			Local line:=_lines[i]
+			
+			If Not line.visible
+				
+				line.rect=rect
+				
+			Else
+				
+				Local size:=MeasureLine( i )
+				
+				maxWidth=Max( maxWidth,size.x )
+				
+				line.maxWidth=maxWidth
+				rect=New Recti( 0,liney,size.x,liney+size.y )
+				line.rect=rect
+				
+				liney+=size.y
+				
+			Endif
+			
+			_lines[i]=line
+			
+		Next
+		
+		If dlines<0
+			
+			Local i:=last
+			While i<_lines.Length+dlines
+				_lines[i]=_lines[i-dlines]
+				i+=1
+			Wend
+			
+			_lines.Resize( _lines.Length+dlines )
+		Endif
+		
+		For Local i:=last Until _lines.Length
+			
+			Local line:=_lines[i]
+			
+			If Not line.visible
+				
+				line.rect=rect
+				
+			Else
+				
+				Local size:=line.rect.Size
+				
+				maxWidth=Max( maxWidth,size.x )
+				
+				line.maxWidth=maxWidth
+				rect=New Recti( 0,liney,size.x,liney+size.y )
+				line.rect=rect
+				
+				liney+=size.y
+				
+			Endif
+			
+			_lines[i]=line
+			
+		Next
+		
+		If _cursor>_doc.TextLength
+			_cursor=_doc.TextLength
+			_anchor=_cursor
+			UpdateCursor()
+			RequestRender()
+		Else
+			_anchor=Min( _anchor,_doc.TextLength )
+		Endif
+		
+'		Print "Document width="+_lines.Top.maxWidth+", height="+_lines.Top.rect.Bottom
+	End
+	
+End

+ 23 - 14
view/TreeViewExt.monkey2

@@ -41,20 +41,12 @@ Class TreeViewExt Extends TreeView
 		
 		Super.NodeExpanded+=Lambda( node:Node )
 			
-			_expandStateChanged=True
-			
-			_expander.Store( node )
-			OnSelect( node )
-			NodeExpanded( node )
+			OnExpanded( node,True )
 		End
 		
 		Super.NodeCollapsed+=Lambda( node:Node )
 			
-			_expandStateChanged=True
-			
-			_expander.Store( node )
-			OnSelect( node )
-			NodeCollapsed( node )
+			OnCollapsed( node,True )
 		End
 		
 		_selColor=App.Theme.GetColor( "panel" )
@@ -114,7 +106,7 @@ Class TreeViewExt Extends TreeView
 						End )
 	End
 	
-	Method FindSubNode:TreeView.Node( whereNode:TreeView.Node,recursive:Bool,findCondition:Bool(n:TreeView.Node) )
+	Method FindSubNode:TreeView.Node( whereNode:TreeView.Node,recursive:Bool,findCondition:Bool(TreeView.Node) )
 	
 		If findCondition( whereNode ) Return whereNode
 	
@@ -147,7 +139,7 @@ Class TreeViewExt Extends TreeView
 	End
 	
 	Method SelectByPathEnds( pathEnding:String )
-	
+		
 		Local n:=FindSubNode( RootNode,
 						True,
 						Lambda:Bool( n:TreeView.Node )
@@ -221,6 +213,24 @@ Class TreeViewExt Extends TreeView
 		
 	End
 	
+	Method OnCollapsed( node:Node,sel:Bool )
+	
+		_expandStateChanged=True
+	
+		_expander.Store( node )
+		If sel Then OnSelect( node )
+		NodeCollapsed( node )
+	End
+	
+	Method OnExpanded( node:Node,sel:Bool )
+	
+		_expandStateChanged=True
+	
+		_expander.Store( node )
+		If sel Then OnSelect( node )
+		NodeExpanded( node )
+	End
+	
 '	Method PrintExpanded()
 '		
 '		_expander.PrintExpanded()
@@ -268,8 +278,7 @@ Class TreeViewExt Extends TreeView
 			n=n.Parent
 		Wend
 	
-		' scroll Y only 
-		Local sx:=Scroll.x
+		' scroll Y only
 		Local scroll:=Scroll
 		Super.EnsureVisible( node.Rect )
 		scroll.Y=Scroll.y