Sfoglia il codice sorgente

Squashed 'src/ted2go/' changes from e3c40042..69447371

69447371 Merge branch 'dev'
e7a2d007 Added 'Cancel' action in Preferences dialog.
00df6473 Improved code templates - added dot-prefixes to show templates for global ident or member of some instance.
34f957e6 Added option "Place opened document to the left side" - it helps to use "Close to the right" tabs action for earlier opened tabs.
b57d6820 Improved autocompletion for pointer-types. Added -> for dereferencing pointer types. Restored logic "save all opened tabs before compilation".
012a638b Added Simple_Mojo3d_app template (it's bloom test file).
e3f73a07 Find next / prev - made centered line with result.
ebfe7b38 Fixed Source tree expanding state (was collapsed after parsing).
dc3b361d Fixed broken jump to found results.
3b6ab39e Added prefs item "Project tree single-click mode".
e0156e50 Fixed #55.
1e268c17 Cleanup.
31723f23 Fixed #58.
12983901 Fixed #57.
3bc695e7 Fixed #56.
d2c3cd0d Fixed #54.
1e2dc920 Done rewriting Project (Explorer) tree logic.
466825a5 Reworked files filtering in Project view.

git-subtree-dir: src/ted2go
git-subtree-split: 6944737130c1a9690fb61a9f071bce90ad215a21
Mark Sibly 7 anni fa
parent
commit
6375193d6e

+ 7 - 1
LiveTemplates.monkey2

@@ -93,6 +93,7 @@ Class LiveTemplatesClass
 	
 	Field _items:=New StringMap<StringMap<String>>
 	Field _dirty:Bool
+	Field _filesTime:=New StringMap<Long>
 	
 	Property DefaultPath:String()
 		Return "asset::liveTemplates.json"
@@ -105,7 +106,12 @@ Class LiveTemplatesClass
 	Method Load( jsonPath:String )
 		
 		If Not FileExists( jsonPath ) Return
-			
+		
+		Local t:=_filesTime[jsonPath]
+		Local t2:=GetFileTime( jsonPath )
+		If t2=t Return
+		_filesTime[jsonPath]=t2
+		
 		Local langs:=Json_LoadObject( jsonPath ).All()
 		For Local i:=Eachin langs
 			Local lang:=i.Key

+ 15 - 13
MainWindow.monkey2

@@ -134,22 +134,15 @@ Class MainWindowInstance Extends Window
 		'Find tab
 		
 		_findConsole=New TreeViewExt
+		_findConsole.SingleClickExpanding=True
 		_findConsole.NodeClicked+=Lambda( node:TreeView.Node )
 		
 			Local n:=Cast<NodeWithData<FileJumpData>>( node )
 			If Not n Return
 			
 			Local data:=n.data
-			
-			Local doc:=_docsManager.OpenDocument( data.path,True )
-			If Not doc Return
-			
-			Local tv:=doc.TextView
-			If Not tv Return
-			
-			UpdateWindow( False )
-			
-			tv.SelectText( data.pos,data.pos+data.len ) 'set cursor here
+			Local pos:=New Vec2i( data.line,data.posInLine )
+			GotoCodePosition( data.path,pos,data.len )
 		End
 		
 		'Help tab
@@ -280,7 +273,7 @@ Class MainWindowInstance Extends Window
 				Local path:=AllocTmpPath( "untitled",ExtractExt( f ) )
 				If Not path Return
 				SaveString( src,path )
-				Local doc:=_docsManager.OpenDocument( path,True )
+				OpenDocument( path,True )
 			End
 		Next
 		
@@ -456,6 +449,7 @@ Class MainWindowInstance Extends Window
 		
 		SetupChatTab()
 		
+		_projectView.SingleClickExpanding=Prefs.MainProjectSingleClickExpanding
 	End
 	
 	Method GainFocus()
@@ -527,6 +521,14 @@ Class MainWindowInstance Extends Window
 		Endif
 	End
 	
+	Method OnDocumentLinesModified( doc:Ted2Document,first:Int,removed:Int,inserted:Int )
+		
+		Local res:=_findActions.lastFindResults
+		If Not res Return
+		
+		res.ProcessLinesModified( doc.Path,first,removed,inserted )
+	End
+	
 	Property Mx2ccPath:String()
 	
 		Return _mx2cc
@@ -1010,7 +1012,7 @@ Class MainWindowInstance Extends Window
 		Endif
 	End
 	
-	Method GotoCodePosition( docPath:String, pos:Vec2i )
+	Method GotoCodePosition( docPath:String,pos:Vec2i,lenToSelect:Int=0 )
 		
 		Local doc:=Cast<CodeDocument>( _docsManager.OpenDocument( docPath,True ) )
 		If Not doc Return
@@ -1020,7 +1022,7 @@ Class MainWindowInstance Extends Window
 		
 		UpdateWindow( False )
 		
-		tv.GotoPosition( pos )
+		tv.GotoPosition( pos,lenToSelect )
 		tv.MakeKeyView()
 	End
 	

+ 8 - 2
Prefs.monkey2

@@ -19,6 +19,8 @@ Class PrefsInstance
 	'
 	Field MainToolBarVisible:=True
 	Field MainProjectIcons:=True
+	Field MainProjectSingleClickExpanding:=False
+	Field MainPlaceDocsAtBegin:=True
 	'
 	Field IrcNickname:String
 	Field IrcServer:="irc.freenode.net"
@@ -71,7 +73,9 @@ Class PrefsInstance
 			Local j2:=json["main"].ToObject()
 			MainToolBarVisible=Json_GetBool( j2,"toolBarVisible",MainToolBarVisible )
 			MainProjectIcons=Json_GetBool( j2,"projectIcons",MainProjectIcons )
-      
+      		MainProjectSingleClickExpanding=Json_GetBool( j2,"singleClickExpanding",MainProjectSingleClickExpanding )
+      		MainPlaceDocsAtBegin=Json_GetBool( j2,"placeDocsAtBegin",MainPlaceDocsAtBegin )
+      		
 		Endif
 		
 		If json.Contains( "completion" )
@@ -126,6 +130,8 @@ Class PrefsInstance
 		json["main"]=j
 		j["toolBarVisible"]=New JsonBool( MainToolBarVisible )
 		j["projectIcons"]=New JsonBool( MainProjectIcons )
+		j["singleClickExpanding"]=New JsonBool( MainProjectSingleClickExpanding )
+		j["placeDocsAtBegin"]=New JsonBool( MainPlaceDocsAtBegin )
 		
 		j=New JsonObject
 		json["irc"]=j
@@ -165,7 +171,7 @@ Class PrefsInstance
 		j["sortByType"]=New JsonBool( SourceSortByType )
 		j["showInherited"]=New JsonBool( SourceShowInherited )
 		
-		If "SiblyMode" json["siblyMode"]=JsonBool.TrueValue
+		If SiblyMode json["siblyMode"]=JsonBool.TrueValue
 		
 	End
 	

+ 2 - 2
Ted2.monkey2

@@ -82,9 +82,9 @@
 #Import "view/ConsoleViewExt"
 #Import "view/ListViewExt"
 #Import "view/AutocompleteView"
-#Import "view/CodeTreeView"
 #Import "view/TreeViewExt"
-#Import "view/FileBrowserExt"
+#Import "view/CodeTreeView"
+'#Import "view/FileBrowserExt"
 #Import "view/CodeGutterView"
 #Import "view/ToolBarViewExt"
 #Import "view/HintView"

+ 1 - 1
action/BuildActions.monkey2

@@ -363,7 +363,7 @@ Class BuildActions Implements IModuleBuilder
 	
 	Method SaveAll:Bool( buildFile:String )
 		
-		Local proj:=ProjectView.FindProjectByFile( buildFile )
+		Local proj:="" 'ProjectView.FindProjectByFile( buildFile )
 		
 		For Local doc:=Eachin _docs.OpenDocuments
 			' save docs only for built project

+ 17 - 15
action/FileActions.monkey2

@@ -336,22 +336,24 @@ Class FileActions
 	Field _prefsDialog:PrefsDialog
 	
 	Method OnPrefs()
-	
-		If Not _prefsDialog
-			_prefsDialog=New PrefsDialog
-			
-			_prefsDialog.Apply+=Lambda()
-			
-				For Local d:=Eachin _docs.OpenDocuments
-					Local tv:=Cast<CodeDocumentView>( d.TextView )
-					If tv Then tv.UpdatePrefs()
-				Next
-				
-				MainWindow.OnPrefsChanged()
-			End
+		
+		If _prefsDialog Then _prefsDialog.Hide()
+		
+		LiveTemplates.Load()
+		
+		_prefsDialog=New PrefsDialog
+		
+		_prefsDialog.Apply+=Lambda()
+		
+			For Local d:=Eachin _docs.OpenDocuments
+				Local tv:=Cast<CodeDocumentView>( d.TextView )
+				If tv Then tv.UpdatePrefs()
+			Next
 			
-		Endif
-		_prefsDialog.Show()
+			MainWindow.OnPrefsChanged()
+		End
+		
+		_prefsDialog.ShowModal()
 	End
 	
 End

+ 58 - 12
action/FindActions.monkey2

@@ -14,6 +14,7 @@ Class FindActions
 	Field findAllInFiles:Action
 	
 	Field options:FindOptions
+	Field lastFindResults:FindResults
 	
 	Method New( docs:DocumentManager,projView:ProjectView,findConsole:TreeViewExt )
 		
@@ -174,7 +175,7 @@ Class FindActions
 			If i=-1 Or i+what.Length>range.y Return
 		Endif
 		
-		tv.SelectText( i,i+what.Length )
+		OnSelectText( tv,i,i+what.Length )
 		
 	End
 	
@@ -222,7 +223,15 @@ Class FindActions
 			Forever
 		End
 		
-		tv.SelectText( i,i+what.Length )
+		OnSelectText( tv,i,i+what.Length )
+	End
+	
+	Method OnSelectText( tv:TextView,anchor:Int,cursor:Int )
+		
+		tv.SelectText( anchor,cursor )
+		
+		Local code:=Cast<CodeTextView>( tv )
+		If code Then code.MakeCentered()
 	End
 	
 	Method OnFindAllInFiles()
@@ -248,6 +257,7 @@ Class FindActions
 			Local filter:=_findInFilesDialog.FilterText
 			
 			Local result:=FindInProject( what,proj,sens,filter )
+			lastFindResults=result
 			
 			If result Then CreateResultTree( _findConsole.RootNode,result,what,proj )
 		End)
@@ -256,7 +266,7 @@ Class FindActions
 	
 	Const DEFAULT_FILES_FILTER:="monkey2" ',txt,htm,html,h,cpp,json,xml,ini"
 	
-	Method FindInProject:StringMap<Stack<FileJumpData>>( what:String,projectPath:String,caseSensitive:Bool,filesFilter:String=DEFAULT_FILES_FILTER )
+	Method FindInProject:FindResults( what:String,projectPath:String,caseSensitive:Bool,filesFilter:String=DEFAULT_FILES_FILTER )
 		
 		If Not filesFilter Then filesFilter=DEFAULT_FILES_FILTER
 		
@@ -270,7 +280,7 @@ Class FindActions
 		Utils.GetAllFiles( projectPath,exts,files )
 		Local len:=what.Length
 		
-		Local result:=New StringMap<Stack<FileJumpData>>
+		Local result:=New FindResults
 		
 		'Local counter:=1
 		Local doc:=New TextDocument 'use it to get line number
@@ -295,6 +305,7 @@ Class FindActions
 				data.pos=i
 				data.len=len
 				data.line=doc.FindLine( i )+1
+				data.posInLine=i-doc.StartOfLine( data.line )
 				
 				items.Add( data )
 				
@@ -349,7 +360,7 @@ Class FindActions
 	End
 	#End
 	
-	Method CreateResultTree( root:TreeView.Node,map:StringMap<Stack<FileJumpData>>,what:String,projectPath:String )
+	Method CreateResultTree( root:TreeView.Node,results:FindResults,what:String,projectPath:String )
 		
 		root.RemoveAllChildren()
 		
@@ -357,9 +368,9 @@ Class FindActions
 		
 		Local subRoot:TreeView.Node
 		
-		For Local file:=Eachin map.Keys
+		For Local file:=Eachin results.Files
 			
-			Local items:=map[file]
+			Local items:=results[file]
 			
 			subRoot=New TreeView.Node( file.Replace( projectPath+"/","" )+" ("+items.Length+")",root )
 	
@@ -467,13 +478,48 @@ Class FindActions
 			
 		Forever
 		
-'		If options.selectionOnly
-'			anchor=range.x
-'			cursor=range.x
-'		Endif
+		OnSelectText( tv,anchor,cursor )
 		
-		tv.SelectText( anchor,cursor )
+	End
+	
+End
+
+
+Class FindResults
+	
+	Operator[]:Stack<FileJumpData>( filePath:String )
+		
+		Return _map[filePath]
+	End
+	
+	Operator[]=( filePath:String,results:Stack<FileJumpData> )
+	
+		_map[filePath]=results
+	End
+	
+	Property Files:StringMap<Stack<FileJumpData>>.KeyIterator()
+		
+		Return _map.Keys.All()
+	End
+	
+	Method ProcessLinesModified( filePath:String,first:Int,removed:Int,inserted:Int )
 		
+		Local list:=_map[filePath]
+		If Not list Return
+		
+		For Local d:=Eachin list
+			If d.line>first Then d.line+=(inserted-removed)
+		Next
 	End
 	
+	Method Empty:Bool()
+		
+		Return _map.Empty
+	End
+	
+	
+	Private
+	
+	Field _map:=New StringMap<Stack<FileJumpData>>
 End
+

+ 84 - 0
assets/newfiles/Simple_Mojo3d_App.monkey2

@@ -0,0 +1,84 @@
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _donut:Model
+	
+	Field _bloom:BloomEffect
+	
+	Method New( title:String="Simple mojo3d app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.ClearColor=Color.Black
+		
+		_bloom=New BloomEffect
+		
+		_scene.AddPostEffect( _bloom )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-10 )
+		
+		'create light
+		'
+		_light=New Light
+
+		_light.RotateX( 90 )
+		
+		Local material:=New PbrMaterial( Color.Black )
+		material.EmissiveFactor=New Color( 0,2,0 )
+		
+		_donut=Model.CreateTorus( 2,.5,48,24,material )
+		
+		_donut.Move( 0,10,0 )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		If Keyboard.KeyHit( Key.Space ) _donut.Visible=Not _donut.Visible
+		
+		_donut.Rotate( .2,.4,.6 )
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 5 - 14
dialog/FindDialog.monkey2

@@ -13,17 +13,7 @@ Class FindDialog Extends DialogExt
 		_findField.Entered+=Lambda()
 			actions.findNext.Trigger()
 		End
-		_findField.TextChanged+=Lambda(  )
-			#Rem
-			Local t:=_findField.Text
-			If t.Length > 1
-				If Not Prefs.SiblyMode
-					actions.FindByTextChanged( EntireProject )
-				Endif
-			Endif
-			#End
-		End
-
+		
 		_findField.Tabbed+=_replaceField.MakeKeyView
 
 		_replaceField.Tabbed+=_findField.MakeKeyView
@@ -94,9 +84,10 @@ Class FindDialog Extends DialogExt
 	End
 	
 	Method SetInitialText( find:String )
-If Not Prefs.SiblyMode
-		_findField.Text=find
-Endif
+
+		If Not Prefs.SiblyMode
+			_findField.Text=find
+		Endif
 		_findField.SelectAll()
 	End
 	

+ 16 - 0
dialog/PrefsDialog.monkey2

@@ -40,6 +40,10 @@ Class PrefsDialog Extends DialogExt
 		
 		ContentView=tabView
 		
+		Local cancel:=AddAction( "Cancel" )
+		cancel.Triggered=Hide
+		SetKeyAction( Key.Escape,cancel )
+		
 		Local apply:=AddAction( "Apply changes" )
 		apply.Triggered=OnApply
 		
@@ -78,6 +82,8 @@ Class PrefsDialog Extends DialogExt
 	
 	Field _mainToolBarVisible:CheckButton
 	Field _mainProjectIcons:CheckButton
+	Field _mainProjectSingleClickExpanding:CheckButton
+	Field _mainPlaceDocsAtBegin:CheckButton
 	
 	Field _monkeyRootPath:TextFieldExt
 	
@@ -120,6 +126,8 @@ Class PrefsDialog Extends DialogExt
 		
 		Prefs.MainToolBarVisible=_mainToolBarVisible.Checked
 		Prefs.MainProjectIcons=_mainProjectIcons.Checked
+		Prefs.MainProjectSingleClickExpanding=_mainProjectSingleClickExpanding.Checked
+		Prefs.MainPlaceDocsAtBegin=_mainPlaceDocsAtBegin.Checked
 		
 		Prefs.IrcNickname=_chatNick.Text
 		Prefs.IrcServer=_chatServer.Text
@@ -145,6 +153,12 @@ Class PrefsDialog Extends DialogExt
 		_mainProjectIcons=New CheckButton( "Project file type icons" )
 		_mainProjectIcons.Checked=Prefs.MainProjectIcons
 		
+		_mainProjectSingleClickExpanding=New CheckButton( "Project tree single-click mode" )
+		_mainProjectSingleClickExpanding.Checked=Prefs.MainProjectSingleClickExpanding
+		
+		_mainPlaceDocsAtBegin=New CheckButton( "Place opened document to the left side" )
+		_mainPlaceDocsAtBegin.Checked=Prefs.MainPlaceDocsAtBegin
+		
 		_monkeyRootPath=New TextFieldExt( Prefs.MonkeyRootPath )
 		_monkeyRootPath.Enabled=False
 		Local chooseMonkeyPath:=New Action( "..." )
@@ -182,6 +196,8 @@ Class PrefsDialog Extends DialogExt
 		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _mainProjectIcons,"top" )
 		docker.AddView( _mainToolBarVisible,"top" )
+		docker.AddView( _mainProjectSingleClickExpanding,"top" )
+		docker.AddView( _mainPlaceDocsAtBegin,"top" )
 		docker.AddView( New Label( " " ),"top" )
 		
 		Return docker

+ 28 - 38
document/CodeDocument.monkey2

@@ -182,24 +182,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		Select event.Type
 		Case EventType.KeyDown,EventType.KeyRepeat
 			
-			Local key:=event.Key
-			
-			'map keypad nav keys...
-			If Not (event.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
-			
-			CheckFormat( event,key )
+			Local key:=FixNumpadKeys( event )
 			
 			Select key
 			
@@ -266,8 +249,11 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 					If shift
 						SmartPaste()
-					Elseif ctrl And CanCopy
-						OnCopy()
+					Elseif ctrl
+						If CanCopy Then OnCopy()
+					Elseif Not alt
+						' text overwrite mode
+						MainWindow.OverwriteTextMode=Not MainWindow.OverwriteTextMode
 					Endif
 					Return
 			
@@ -473,6 +459,9 @@ Class CodeDocumentView Extends Ted2CodeTextView
 						Endif
 			
 					Endif
+					
+					CheckFormat( event )
+					
 					Return
 			
 			
@@ -505,8 +494,6 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 		Case EventType.KeyChar
 			
-			CheckFormat( event,event.Key )
-			
 			If event.Key = Key.Space And ctrl
 				If _doc.CanShowAutocomplete()
 					Local ident:=IdentBeforeCursor()
@@ -602,9 +589,11 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		Super.OnKeyEvent( event )
 		
+		CheckFormat( event )
+		
 		'show autocomplete list after some typed chars
 		If event.Type = EventType.KeyChar
-		
+			
 			If _doc.CanShowAutocomplete()
 				'preprocessor
 				If event.Text = "#"
@@ -640,12 +629,6 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		Endif
 		
-		' text overwrite mode
-		If event.Type=EventType.KeyDown And event.Key=Key.Insert And Not (shift Or ctrl Or alt)
-			
-			MainWindow.OverwriteTextMode=Not MainWindow.OverwriteTextMode
-		Endif
-		
 	End
 	
 	Method ShowJsonDialog()
@@ -810,6 +793,7 @@ Class CodeDocument Extends Ted2Document
 			If _debugLine>=first
 				_debugLine+=(inserted-removed)
 			Endif
+			
 		End
 		
 		_doc.TextChanged+=Lambda()
@@ -1137,7 +1121,7 @@ Class CodeDocument Extends Ted2Document
 		
 		Local frame:=AutoComplete.Frame
 		
-		Local w:=frame.Width+ScaledVal( 18 ) 'hack: 18px for scroll
+		Local w:=frame.Width
 		Local h:=frame.Height
 		
 		Local cursorRect:=_codeView.CursorRect
@@ -1214,7 +1198,6 @@ Class CodeDocument Extends Ted2Document
 	Field _timer:Timer
 	Field _parser:ICodeParser
 	Field _prevLine:=-1
-	Field _prevScope:CodeItem
 	Field _parsingEnabled:Bool
 	
 	Field _toolBar:ToolBarExt
@@ -1319,6 +1302,12 @@ Class CodeDocument Extends Ted2Document
 		
 		ParsingDoc() 'start parsing right after loading, not by timer
 		
+		' grab lines after load
+		_doc.LinesModified+=Lambda( first:Int,removed:Int,inserted:Int )
+			
+			MainWindow.OnDocumentLinesModified( Self,first,removed,inserted )
+		End
+		
 		Return True
 	End
 	
@@ -1349,20 +1338,21 @@ Class CodeDocument Extends Ted2Document
 		
 		If Not _parsingEnabled Return
 		
-		Local scope:=_parser.GetScope( Path,_codeView.LineNumAtCursor+1 )	
-		If scope And scope <> _prevScope
-			Local classs := (_prevScope And scope.IsLikeClass And scope = _prevScope.Parent)
-			_prevScope = scope
-			If classs Return 'don't select parent class scope if we are inside of it
+		OnUpdateCurrentScope()
+	End
+	
+	Method OnUpdateCurrentScope()
+		
+		Local scope:=_parser.GetScope( Path,_codeView.LineNumAtCursor+1 )
+		If scope
 			_treeView.SelectByScope( scope )
-			_prevScope = scope
 		Endif
-		
 	End
 	
 	Method UpdateCodeTree()
 		
 		_treeView.Fill( FileExtension,Path )
+		OnUpdateCurrentScope()
 	End
 	
 	Field _timeTextChanged:=0

+ 5 - 4
document/DocumentManager.monkey2

@@ -91,7 +91,7 @@ Class DocumentManager
 		Return _openDocs.ToArray()
 	End
 	
-	Method OpenDocument:Ted2Document( path:String,makeCurrent:Bool=False )
+	Method OpenDocument:Ted2Document( path:String,makeCurrent:Bool=False,openByHand:Bool=True )
 	
 		path=RealPath( path )
 		
@@ -112,7 +112,8 @@ Class DocumentManager
 		InitDoc( doc )
 	
 		_openDocs.Add( doc )
-		Local tab:=_tabView.AddTab( TabText( doc ),doc.View )
+		Local addAtBegin:=(openByHand And Prefs.MainPlaceDocsAtBegin)
+		Local tab:=_tabView.AddTab( TabText( doc ),doc.View,False,addAtBegin )
 		tab.DoubleClicked+=Lambda()
 			DocumentDoubleClicked( doc )
 		End
@@ -212,7 +213,7 @@ Class DocumentManager
 	End
 		
 	Method LoadState( jobj:JsonObject )
-		 
+		
 		If Not jobj.Contains( "openDocuments" ) Return
 		
 		For Local doc:=Eachin jobj.GetArray( "openDocuments" )
@@ -221,7 +222,7 @@ Class DocumentManager
 			Local path:=arr[0]
 			If GetFileType( path )<>FileType.File Continue
 			
-			Local tdoc:=OpenDocument( path,True )
+			Local tdoc:=OpenDocument( path,True,False )
 			If tdoc
 				tdoc.Dirty=MainWindow.IsTmpPath( path )
 				If arr.Length>1

+ 4 - 1
parser/CodeItem.monkey2

@@ -420,11 +420,12 @@ Struct CodeType
 	Field kind:String
 	Field expr:String
 	Field args:CodeType[]
+	Field isPointer:Bool
 	
 	Property ident:String()
 		Return _ident
 	Setter( value:String )
-		_ident = FixTypeIdent( value )
+		_ident=FixTypeIdent( value )
 	End
 	
 	Property IsLikeFunc:Bool()
@@ -450,6 +451,8 @@ Struct CodeType
 			_str=ident
 		Endif
 		
+		If isPointer Then _str+=" Ptr"
+		
 		Return _str
 	End
 	

+ 25 - 33
parser/Monkey2Parser.monkey2

@@ -17,18 +17,10 @@ Function FixTypeIdent:String( ident:String )
 	Select ident
 	Case "new","bool","byte","double","float","int","long","object","short","string","throwable","variant","void","array"
 		Return ident.Slice( 0,1 ).ToUpper()+ident.Slice( 1 )
+	Case "cstring","ubyte","uint","ulong","ushort"
+		Return ident.Slice( 0,2 ).ToUpper()+ident.Slice( 2 )
 	Case "typeinfo"
 		Return "TypeInfo"
-	Case "cstring"
-		Return "CString"
-	Case "ubyte"
-		Return "UByte"
-	Case "uint"
-		Return "UInt"
-	Case "ulong"
-		Return "ULong"
-	Case "ushort"
-		Return "UShort"
 	End
 	Return ident
 End
@@ -859,7 +851,7 @@ Class Monkey2Parser Extends CodeParserPlugin
 				t.kind=kind
 				t.ident=type["ident"].ToString()
 				Return t
-										
+				
 			Case "functype"
 				'retType
 				'params
@@ -875,7 +867,7 @@ Class Monkey2Parser Extends CodeParserPlugin
 					Endif
 				Endif
 				Return t
-						
+				
 			Case "generic"
 				'expr
 				'args
@@ -897,7 +889,7 @@ Class Monkey2Parser Extends CodeParserPlugin
 				Endif
 				
 				Return t
-					
+				
 			Case "member"
 			
 				Local t:=ParseMember( type )
@@ -922,14 +914,14 @@ Class Monkey2Parser Extends CodeParserPlugin
 					Local tp:=type["type"].ToObject()
 					Local t:=ParseType( tp )
 					If t<>Null
-						t.ident+=" Ptr"
+						t.isPointer=True
 						Return t
 					Endif
 				Endif
-					
+				
 			Default
 			
-				
+			
 		End
 		
 		Return Null
@@ -1263,23 +1255,23 @@ End
 
 Struct Chars
 	
-	Const SINGLE_QUOTE:=39
-	Const DOUBLE_QUOTE:=34
-	Const COMMA:=44
-	Const DOT:=46
-	Const EQUALS:=61
-	Const LESS_BRACKET:=60
-	Const MORE_BRACKET:=62
-	Const OPENED_SQUARE_BRACKET:=91
-	Const CLOSED_SQUARE_BRACKET:=93
-	Const OPENED_ROUND_BRACKET:=40
-	Const CLOSED_ROUND_BRACKET:=41
-	Const DIGIT_0:=48
-	Const DIGIT_9:=57
-	Const AT:=64
-	Const GRID:=35 ' #
-	Const TAB:=9
-	Const SPACE:=32
+	Const SINGLE_QUOTE:="'"[0] '39
+	Const DOUBLE_QUOTE:="~q"[0] '34
+	Const COMMA:=","[0] '44
+	Const DOT:="."[0] '46
+	Const EQUALS:="="[0] '61
+	Const LESS_BRACKET:="<"[0] '60
+	Const MORE_BRACKET:=">"[0] '62
+	Const OPENED_SQUARE_BRACKET:="["[0] '91
+	Const CLOSED_SQUARE_BRACKET:="]"[0] '93
+	Const OPENED_ROUND_BRACKET:="("[0] '40
+	Const CLOSED_ROUND_BRACKET:=")"[0] '41
+	Const DIGIT_0:="0"[0] '48
+	Const DIGIT_9:="9"[0] '57
+	Const AT:="@"[0] '64
+	Const GRID:="#"[0] '35
+	Const TAB:="~t"[0] '9
+	Const SPACE:=" "[0] '32
 	
 End
 

+ 2 - 2
project.json

@@ -1,3 +1,3 @@
 {
-    "exclude":[".git", "bin", "*.buildv*", "*.products", "logo"]
-}
+    "exclude":[".git*", "bin", "*.buildv*", "*.products", "logo"]
+}

+ 1 - 1
syntax/CppHighlighter.monkey2

@@ -15,7 +15,7 @@ Class CppHighlighter Extends HighlighterPlugin
 		
 	Method New()
 		Super.New()
-		_types=New String[]( ".cpp",".h",".hpp",".c" )
+		_types=New String[]( ".cpp",".h",".hpp",".c",".js" )
 		_hl=New Highlighter
 		_hl.Painter=HL
 	End

+ 4 - 0
syntax/Monkey2Formatter.monkey2

@@ -34,6 +34,9 @@ Class Monkey2CodeFormatter Extends CodeFormatterPlugin
 		
 		'find start of ident
 		'
+		While start And Not IsIdent( text[start-1] ) 'skip space/tab/.,(....
+			start-=1
+		Wend
 		While start And IsIdent( text[start-1] )
 			start-=1
 		Wend
@@ -84,6 +87,7 @@ Class Monkey2CodeFormatter Extends CodeFormatterPlugin
 	Method FormatLine( view:CodeTextView,line:Int )
 		
 		Local doc:=view.Document
+		If line>=doc.NumLines Return
 		
 		'ignore comments...
 		'

+ 5 - 2
testing/ParserTests.monkey2

@@ -11,10 +11,13 @@ Class TestTheSame
 		Return Null
 	End
 	
-	Method Test()
-		
+	Method Test( pType:String Ptr )
+		pType->Capitalize()
+		aPtr->Normalize()
 	End
 	
+	Field aPtr:Vec2i Ptr
+	
 End
 
 Struct Vec2i Extension

+ 10 - 0
utils/Utils.monkey2

@@ -253,6 +253,16 @@ Class Stack<T> Extension
 		If Not Self.Contains( value ) Then Self.Add( value )
 	End
 	
+	Operator+=( item:T )
+		
+		Self.Add( item )
+	End
+	
+	Operator-=( item:T )
+	
+		Self.Remove( item )
+	End
+	
 End
 
 

+ 24 - 11
view/AutocompleteView.monkey2

@@ -124,7 +124,7 @@ Class AutocompleteDialog Extends NoTitleDialog
 			_disableUsingsFilter=False
 		End
 		
-		OnThemeChanged()
+		OnValidateStyle()
 	End
 	
 	Property DisableUsingsFilter:Bool()
@@ -151,6 +151,8 @@ Class AutocompleteDialog Extends NoTitleDialog
 	
 	Method Show( ident:String,filePath:String,fileType:String,docLineNum:Int,docLineStr:String,docPosInLine:Int )
 		
+		OnValidateStyle()
+		
 		Local dotPos:=ident.FindLast( "." )
 		
 		' using lowerCase for keywords
@@ -298,24 +300,35 @@ Class AutocompleteDialog Extends NoTitleDialog
 		' hide to re-layout on open
 		If IsOpened Then Hide()
 		
-		' nothing to show
-		If result.Empty
-			Return
-		Endif
-		
 		If lastIdent Then CodeItemsSorter.SortByIdent( result,lastIdent )
 		
 		'-----------------------------
 		' live templates
 		'-----------------------------
-		If onlyOne And Prefs.AcUseLiveTemplates
-			For Local i:=Eachin GetTemplates( fileType )
-				If i.Text.StartsWith( lastIdent )
+		If Prefs.AcUseLiveTemplates
+			Local list:=GetTemplates( fileType )
+			For Local i:=Eachin list
+				Local templ:=i.Text
+				Local withDot:=templ.StartsWith( "." )
+				Local withDot2:=templ.StartsWith( ".." )
+				If onlyOne And withDot2 Continue ' skip if it requires instance
+				If Not onlyOne And Not withDot Continue ' skip if it doesn't require instance
+				If withDot2
+					templ=templ.Slice( 2 )
+				Elseif withDot
+					templ=templ.Slice( 1 )
+				Endif
+				If lastIdent And templ.StartsWith( lastIdent )
 					result.Insert( 0,i )
 				Endif
 			Next
 		Endif
 		
+		' nothing to show
+		If result.Empty
+			Return
+		Endif
+		
 		_view.Reset()'reset selIndex
 		_view.SetItems( result )
 		
@@ -327,14 +340,14 @@ Class AutocompleteDialog Extends NoTitleDialog
 	
 	Protected
 	
-	Method OnThemeChanged() Override
+	Method OnValidateStyle() Override
 		
 		_view.MaxSize=(App.Theme.Scale.x>1) ? _etalonMaxSize*App.Theme.Scale Else _etalonMaxSize
 	End
 	
 	Private
 	
-	Field _etalonMaxSize:Vec2i
+	Field _etalonMaxSize:Vec2f
 	Field _view:AutocompleteListView
 	Field _keywords:StringMap<Stack<ListViewItem>>
 	Field _templates:StringMap<Stack<ListViewItem>>

+ 49 - 16
view/CodeTextView.monkey2

@@ -20,6 +20,7 @@ Class CodeTextView Extends TextView
 		CursorMoved += OnCursorMoved
 		Document.TextChanged += TextChanged
 		
+		
 '		Document.LinesModified += Lambda( first:Int,removed:Int,inserted:Int )
 '			
 '			If _extraSelStart=-1 Return
@@ -98,10 +99,16 @@ Class CodeTextView Extends TextView
 		
 		While n >= start
 			
-			Local q:=(text[n] = "?"[0])
-			If text[n] = Chars.DOT Or q ' . or ?.
+			Local more:=(text[n]=Chars.MORE_BRACKET)
+			
+			If text[n] = Chars.DOT Or more ' . | ?. | ->
 				If Not withDots Exit
-				If q And text[n+1] <> Chars.DOT Exit
+				If more
+					If n>0 And text[n-1]<>"-"[0] Exit
+					n-=1 ' skip '-'
+				Else
+					If n>0 And text[n-1]="?"[0] Then n-=1 ' skip '?'
+				Endif
 			ElseIf Not (IsIdent( text[n] ) Or text[n] = Chars.GRID) ' #
 				Exit
 			Endif
@@ -110,7 +117,11 @@ Class CodeTextView Extends TextView
 		Wend
 		n+=1
 		
-		Return (n < cur) ? text.Slice( n,cur ).Replace( "?.","." ) Else ""
+		Local s:=""
+		If n < cur
+			s=text.Slice( n,cur ).Replace( "?.","." ).Replace( "->","." )
+		Endif
+		Return s
 	End
 	
 	Property WordAtCursor:String()
@@ -204,14 +215,14 @@ Class CodeTextView Extends TextView
 		Return n
 	End
 
-	Method GotoPosition( pos:Vec2i )
+	Method GotoPosition( pos:Vec2i,lenToSelect:Int=0 )
 	
-		If pos.y = 0
-			GotoLine( pos.x )
-		Else
+		'If pos.y = 0
+		'	GotoLine( pos.x )
+		'Else
 			Local dest:=Document.StartOfLine( pos.x )+pos.y
-			SelectText( dest,dest )
-		Endif
+			SelectText( dest,dest+lenToSelect )
+		'Endif
 		
 		MakeCentered()
 	End
@@ -298,7 +309,8 @@ Class CodeTextView Extends TextView
 	
 	Protected
 	
-	Method CheckFormat( event:KeyEvent,key:Key )
+	Method CheckFormat( event:KeyEvent )
+		
 		
 		Select event.Type
 		
@@ -309,19 +321,20 @@ Class CodeTextView Extends TextView
 				Else
 					If _typing Then FormatWord()
 				Endif
-		
+				
 			Case EventType.KeyDown
 				
+				local key:=FixNumpadKeys( event )
 				Select key
-		
+					
 					Case Key.Tab
 						If _typing Then FormatWord() ' like for Key.Space
-		
+					
 					Case Key.Backspace,Key.KeyDelete,Key.Enter,Key.KeypadEnter
 						_typing=True
-		
+					
 				End
-		
+				
 		End
 	End
 	
@@ -699,3 +712,23 @@ Class MouseEvent Extension
 		Return New MouseEvent( Self.Type,Self.View,location,Self.Button,Self.Wheel,Self.Modifiers,Self.Clicks )
 	End
 End
+
+
+Function FixNumpadKeys:Key( event:KeyEvent )
+	
+	Local key:=event.Key
+	If Not (event.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
+	Return key
+End

+ 8 - 75
view/CodeTreeView.monkey2

@@ -7,16 +7,8 @@ Class CodeTreeView Extends TreeViewExt
 	Field SortByType:=True
 	Field ShowInherited:=False
 	
-	
-	Method New()
-		
-		_expander=New TreeViewExpander( Self )
-	End
-	
 	Method Fill( fileType:String,path:String,expandIfOnlyOneItem:Bool=True )
 	
-		_expander.Store()
-		
 		Local stack:=New Stack<TreeView.Node>
 		Local parser:=ParsersManager.Get( fileType )
 		Local node:=RootNode
@@ -59,18 +51,23 @@ Class CodeTreeView Extends TreeViewExt
 		Local node:=FindNode( RootNode,scope )
 		If Not node Return
 		
+		If scope.IsLikeClass
+			node.Expanded=True
+			NodeExpanded( node )
+			Return
+		Endif
+		
 		Selected=node
 	End
 	
 	
 	Private
 	
-	Field _expander:TreeViewExpander
 	Field _stack:=New Stack<CodeItem>
 	
 	Method FindNode:TreeView.Node( treeNode:TreeView.Node,item:CodeItem )
 	
-		Local node:=Cast<CodeTreeNode>(treeNode)
+		Local node:=Cast<CodeTreeNode>( treeNode )
 		
 		If node And node.CodeItem = item Return node
 	
@@ -90,7 +87,7 @@ Class CodeTreeView Extends TreeViewExt
 		Local n:=New CodeTreeNode( item,node )
 		
 		' restore expand state
-		_expander.RestoreNode( n )
+		_expander.Restore( n )
 		
 		If item.Children = Null And Not ShowInherited Return
 		
@@ -192,67 +189,3 @@ Class CodeTreeNode Extends TreeView.Node
 	Field _code:CodeItem
 	
 End
-
-
-Class TreeViewExpander
-	
-	Method New( tree:TreeView )
-		
-		_tree=tree
-	End
-	
-	Method Store()
-	
-		_expands.Clear()
-		StoreNode( _tree.RootNode )
-	End
-	
-	Method Restore()
-	
-		RestoreNode( _tree.RootNode )
-	End
-	
-	Method RestoreNode( node:TreeView.Node )
-	
-		Local key:=GetNodePath( node )
-		node.Expanded=_expands[key]
-		
-		If node.Children = Null Return
-		
-		For Local i:=Eachin node.Children
-			RestoreNode( i )
-		Next
-	End
-	
-	Private
-	
-	Field _tree:TreeView
-	Field _expands:=New StringMap<Bool>
-	
-	Method GetNodePath:String( node:TreeView.Node )
-	
-		Local s:=node.Text
-		Local i:=node.Parent
-		While i <> Null
-			s=i.Text+"\"+s
-			i=i.Parent
-		Wend
-		Return s
-	End
-	
-	Method StoreNode( node:TreeView.Node )
-	
-		If Not node.Expanded Return
-	
-		Local key:=GetNodePath( node )
-		_expands[key]=node.Expanded
-	
-		If node.Children = Null Return
-	
-		For Local i:=Eachin node.Children
-			StoreNode( i )
-		Next
-	End
-	
-End
-

+ 101 - 82
view/FileBrowserExt.monkey2

@@ -1,4 +1,4 @@
-
+#Rem
 Namespace ted2go
 
 
@@ -12,6 +12,8 @@ Class FileBrowserExt Extends TreeViewExt
 	
 	Method New( rootPath:String="." )
 		
+		Super.New()
+		
 		Style=GetStyle( "FileBrowser" )
 		
 		GetFileTypeIcons()
@@ -50,159 +52,175 @@ Class FileBrowserExt Extends TreeViewExt
 	
 	#rem monkeydoc Updates the browser.
 	#end
-	Method Update()
+	Method Update( node:TreeView.Node=Null )
 	
 		_expander.Store()
+		Local selPath:=Selected ? GetNodePath( Selected ) Else ""
 		
-		UpdateNode( _rootNode,_rootPath,True )
+		Local n:=Cast<FileBrowserExt.Node>( node )
+		If Not n Then n=_rootNode
 		
-		_expander.Restore()
-	End
-	
-	Protected
-	
-	Method OnValidateStyle() Override
-
-		Super.OnValidateStyle()
-			
-		GetFileTypeIcons()
+		UpdateNode( n,True )
+		
+		If selPath Then SelectByPath( selPath )
 		
-		_dirIcon=_fileTypeIcons["._dir"]
-		_fileIcon=_fileTypeIcons["._file"]
 	End
 	
-	Private
+	Protected
 	
 	Class Node Extends TreeView.Node
 	
 		Method New( parent:Node )
 			Super.New( "",parent )
 		End
-		
+	
 		Property Path:String()
 			Return _path
 		End
-		
+	
 		Private
-		
+	
 		Field _path:String
 	End
 	
-	Field _rootNode:Node
-	Field _rootPath:String
-	
-	Field _dirIcon:Image
-	Field _fileIcon:Image
-	
-	Field _expander:TreeViewExpander
 	
-	Method OnNodeClicked( tnode:TreeView.Node )
 	
-		Local node:=Cast<Node>( tnode )
-		If Not node Return
+	Method OnValidateStyle() Override
+
+		Super.OnValidateStyle()
 		
-		FileClicked( node._path )
-	End
-	
-	Method OnNodeRightClicked( tnode:TreeView.Node )
-	
-		Local node:=Cast<Node>( tnode )
-		If Not node Return
+		GetFileTypeIcons()
 		
-		FileRightClicked( node._path )
+		_dirIcon=_fileTypeIcons["._dir"]
+		_fileIcon=_fileTypeIcons["._file"]
 	End
 	
-	Method OnNodeDoubleClicked( tnode:TreeView.Node )
 	
-		Local node:=Cast<Node>( tnode )
-		If Not node Return
-		
-		FileDoubleClicked( node._path )
-	End
-	
-	Method OnNodeExpanded( tnode:TreeView.Node )
+	Private
 	
-		Local node:=Cast<Node>( tnode )
-		If Not node Return
-		
-		UpdateNode( node,node._path,True )
-	End
+	Field _rootNode:Node
+	Field _rootPath:String
 	
-	Method OnNodeCollapsed( tnode:TreeView.Node )
+	Field _dirIcon:Image
+	Field _fileIcon:Image
 	
-		Local node:=Cast<Node>( tnode )
-		If Not node Return
-		
-		For Local child:=Eachin node.Children
-			child.RemoveAllChildren()
-		Next
-		
-	End
+	Field _expander:TreeViewExpander
 	
-	Method UpdateNode( node:Node,path:String,recurse:Bool )
+	Method UpdateNode( node:Node,recurse:Bool=True )
 	
+		Local path:=node._path
+		Print "update node: "+path
 		If Not path.EndsWith( "/" ) path+="/"
 		Local dir:=filesystem.LoadDir( path )
-		
+	
 		Local dirs:=New Stack<String>
 		Local files:=New Stack<String>
-		
+	
 		For Local f:=Eachin dir
-		
+	
 			Local fpath:=path+f
-			
+	
 			Select GetFileType( fpath )
 			Case FileType.Directory
-				dirs.Push( f )
+				dirs.Add( f )
 			Default
-				files.Push( f )
+				files.Add( f )
 			End
 		Next
-		
+	
 		dirs.Sort()
 		files.Sort()
-		
+	
 		Local i:=0,children:=node.Children
-		
+	
 		While i<dir.Length
-		
+	
 			Local f:=""
 			If i<dirs.Length f=dirs[i] Else f=files[i-dirs.Length]
-			
+	
 			Local child:Node
-			
+	
 			If i<children.Length
 				child=Cast<Node>( children[i] )
+				child.RemoveAllChildren()
 			Else
 				child=New Node( node )
 			Endif
-			
+	
 			Local fpath:=path+f
-			
+	
 			child.Text=f
 			child._path=fpath
-			
+	
 			Local icon:Image
-			If Prefs.MainProjectIcons Then 'Only load icon if settings say so
+			If Prefs.MainProjectIcons 'Only load icon if settings say so
 				icon=GetFileTypeIcon( fpath )
 			Endif
-			
+	
 			If i<dirs.Length
-				If Not icon And Prefs.MainProjectIcons icon=_dirIcon
+				If Not icon And Prefs.MainProjectIcons Then icon=_dirIcon
 				child.Icon=icon
+	
+				_expander.SetExpandedState( child )
+	
 				If child.Expanded Or recurse
-					UpdateNode( child,fpath,child.Expanded )
+					UpdateNode( child,child.Expanded )
 				Endif
 			Else
-				If Not icon And Prefs.MainProjectIcons icon=_fileIcon
+				If Not icon And Prefs.MainProjectIcons Then icon=_fileIcon
 				child.Icon=icon
 				child.RemoveAllChildren()
 			Endif
-			
+	
 			i+=1
 		Wend
-		
+	
 		node.RemoveChildren( i )
+	
+	End
+	
+	Method OnNodeClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileClicked( node._path )
+	End
+	
+	Method OnNodeRightClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileRightClicked( node._path )
+	End
+	
+	Method OnNodeDoubleClicked( tnode:TreeView.Node )
+		
+		If tnode.Children.Length>0 Return
+		
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileDoubleClicked( node._path )
+	End
+	
+	Method OnNodeExpanded( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		UpdateNode( node,True )
+	End
+	
+	Method OnNodeCollapsed( tnode:TreeView.Node )
+	
+'		Local node:=Cast<Node>( tnode )
+'		If Not node Return
+'		
+'		For Local child:=Eachin node.Children
+'			child.RemoveAllChildren()
+'		Next
 		
 	End
 	
@@ -254,3 +272,4 @@ Class FileBrowserExt Extends TreeViewExt
 	Global _fileTypeIcons:StringMap<Image>
 	
 End
+#End

+ 41 - 43
view/ListViewExt.monkey2

@@ -53,10 +53,6 @@ Class ListViewExt Extends ScrollableView
 		_lineHeightEtalon=lineHeight
 		_maxLines=maxLines
 		
-		_items=New List<ListViewItem>
-		
-		'MaxSize=New Vec2i( width,height )
-		
 		_selColor=App.Theme.GetColor( "content" )
 		_hoverColor=App.Theme.GetColor( "knob" )
 		
@@ -65,18 +61,16 @@ Class ListViewExt Extends ScrollableView
 	
 	Method AddItems( items:Stack<ListViewItem> )
 		
-		For Local i:=Eachin items
-			_items.AddLast( i )
-		Next
-		_count=_items.Count()
-		_visibleCount=Min( _maxLines,_count )
+		_items.AddAll( items )
+		
+		_visibleCount=Min( _maxLines,_items.Length )
 	End
 	
 	Method AddItem( item:ListViewItem )
 	
-		_items.AddLast( item )
-		_count=_items.Count()
-		_visibleCount=Min( _maxLines,_count )
+		_items.Add( item )
+		
+		_visibleCount=Min( _maxLines,_items.Length )
 	End
 	
 	Method SetItems<T>( items:Stack<T> ) Where T Extends ListViewItem
@@ -115,8 +109,8 @@ Class ListViewExt Extends ScrollableView
 	
 	Property CurrentItem:ListViewItem()
 		
-		Assert( _selIndex >= 0 And _selIndex < _count,"Index out of bounds!" )
-		Return Utils.ValueAt<ListViewItem>( _items,_selIndex )
+		Assert( _selIndex >= 0 And _selIndex < _items.Length,"Index out of bounds!" )
+		Return _items[_selIndex]
 	End
 	
 	Property MoveCyclic:Bool()
@@ -143,8 +137,8 @@ Class ListViewExt Extends ScrollableView
 	
 	Method SelectLast()
 		
-		If _selIndex = _count-1 Return
-		_selIndex=_count-1
+		If _selIndex = _items.Length-1 Return
+		_selIndex=_items.Length-1
 		EnsureVisible()
 		RequestRender()
 	End
@@ -161,13 +155,13 @@ Class ListViewExt Extends ScrollableView
 	Method PageDown()
 	
 		_selIndex+=_visibleCount
-		If _selIndex >= _count Then _selIndex=_count-1
+		If _selIndex >= _items.Length Then _selIndex=_items.Length-1
 	
 		EnsureVisible()
 		RequestRender()
 	End
 	
-	Property Items:List<ListViewItem>.Iterator()
+	Property Items:Stack<ListViewItem>.Iterator()
 		Return _items.All()
 	End
 	
@@ -199,7 +193,7 @@ Class ListViewExt Extends ScrollableView
 	
 	Method SelectNextInternal( ensureVis:Bool )
 	
-		If _selIndex >= _count-1
+		If _selIndex >= _items.Length-1
 			If MoveCyclic Then SelectFirst()
 			Return
 		Endif
@@ -213,10 +207,10 @@ Class ListViewExt Extends ScrollableView
 	
 	Method EnsureVisible()
 		
-		Local clip:=VisibleRect
+		Local clip:=ClipRect+Scroll
 		
 		Local firstVisLine:=Max( clip.Top/_lineH,0 )
-		Local lastVisLine:=Min( (clip.Bottom-1)/_lineH,_count )
+		Local lastVisLine:=Min( (clip.Bottom-1)/_lineH,_items.Length )
 		
 		If _selIndex < firstVisLine
 			Local d:=(firstVisLine-_selIndex)*_lineH
@@ -229,30 +223,24 @@ Class ListViewExt Extends ScrollableView
 
 	Method OnRender( canvas:Canvas ) Override
 	
-		Local clip:=VisibleRect
-		
-		'draw mouse hover
-'		If Rect.Contains( MouseLocation ) 
-'			Local yy:Int=MouseLocation.y/_lineH
-'			canvas.Color=_hoverColor
-'			canvas.DrawRect( clip.Left,yy*_lineH,clip.Width,_lineH )
-'		Endif
+		Local clip:=ClipRect+Scroll
 		
 		Local firstVisLine:=Max( clip.Top/_lineH,0 )
-		Local lastVisLine:=Min( (clip.Bottom-1)/_lineH,_count )
+		Local lastVisLine:=Min( (clip.Bottom-1)/_lineH,_items.Length )
 		
 		Local posY:=_lineH/2,k:=0
+		Local left:=clip.Left-Scroll.x*2
 		For Local item:=Eachin _items
 			If k >= firstVisLine
 				If k > lastVisLine Then Return
 				'draw selection
 				If k = _selIndex
 					canvas.Color=_selColor
-					canvas.DrawRect( clip.Left,posY-_lineH/2,clip.Width,_lineH )
+					canvas.DrawRect( left,posY-_lineH/2,_width,_lineH )
 				End
 				'draw item
 				canvas.Color=Color.White
-				DrawItem( item,canvas,clip.Left+5,posY,0,0.5 )
+				DrawItem( item,canvas,left+5,posY,0,0.5 )
 				posY+=_lineH
 			Endif
 			k+=1
@@ -266,19 +254,29 @@ Class ListViewExt Extends ScrollableView
 		For Local i:=Eachin _items
 			w=Max( w,Int(RenderStyle.Font.TextWidth( i.Text )) )
 		Next
-		
-		w=Min( w,MaxSize.x )
+		w+=50 '+20 for icons
 		_width=w
 		
-		Return New Vec2i( w,_count*_lineH )
+		Local h:=_items.Length*_lineH
+		_height=h
+		
+		Return New Vec2i( w,h )
 	End
 	
 	Method OnMeasure:Vec2i() Override
 		
-		Local h:=Min( _visibleCount*_lineH,MaxSize.y )
-		h=(h/_lineH)*_lineH
+		Local maxH:=Min( _visibleCount*_lineH,MaxSize.y )
+		
+		Local sx:=(_width > MaxSize.x)
+		Local sy:=(_height > maxH)
+		Local w:=Min( _width,MaxSize.x )
+		Local h:=_height
+		If sy
+			h=(maxH/_lineH)*_lineH
+		Endif
+		If sx Then h+=_lineH
 		
-		Return New Vec2i( _width+40,h ) '+40 for icon + scrollbar
+		Return New Vec2i( w,h ) '+20 for scrollbar
 	End
 	
 	Method OnContentMouseEvent( event:MouseEvent ) Override
@@ -297,7 +295,7 @@ Class ListViewExt Extends ScrollableView
 		
 			Local index:=(MouseLocation.y+Scroll.y)/_lineH
 			
-			If index < 0 Or index >= _count Return
+			If index < 0 Or index >= _items.Length Return
 			
 			_selIndex=index
 			OnItemChoosen()
@@ -306,7 +304,7 @@ Class ListViewExt Extends ScrollableView
 		
 			Local index:=(MouseLocation.y+Scroll.y)/_lineH
 			
-			If index < 0 Or index >= _count Return
+			If index < 0 Or index >= _items.Length Return
 			
 			_selIndex=index
 			OnItemChoosen()
@@ -320,13 +318,13 @@ Class ListViewExt Extends ScrollableView
 	
 	Private
 	
-	Field _items:List<ListViewItem>
+	Field _items:=New Stack<ListViewItem>
 	Field _lineH:Int,_lineHeightEtalon:Int
-	Field _count:Int,_visibleCount:Int
+	Field _visibleCount:Int
 	Field _maxLines:Int
 	Field _selIndex:Int
 	Field _selColor:Color,_hoverColor:Color
-	Field _width:Int
+	Field _width:Int,_height:Int
 	Field _moveCyclic:Bool
 	
 End

+ 371 - 85
view/ProjectBrowserView.monkey2

@@ -2,68 +2,143 @@
 Namespace ted2go
 
 
-Class ProjectBrowserView Extends FileBrowserExt
+Class ProjectBrowserView Extends TreeViewExt
 	
-	Field RequestedDelete:Void( path:String )
+	Field RequestedDelete:Void( node:Node )
+	Field FileClicked:Void( node:Node )
+	Field FileRightClicked:Void( node:Node )
+	Field FileDoubleClicked:Void( node:Node )
 	
-	Method New( rootPath:String )
+	Method New()
 		
-		Super.New( rootPath )
+		Super.New()
 		
-		RootNode.Text=StripDir( rootPath )+" ("+rootPath+")"
-		UpdateRootIcon()
+		Style=GetStyle( "FileBrowser" )
 		
-		NodeExpanded+=Lambda( node:TreeView.Node )
-			
-			If node = RootNode And node.Expanded Then Refresh( False )
-		End
+		_rootNode=New Node( Null )
+		RootNode=_rootNode
+		RootNode.Expanded=True
+		RootNodeVisible=False
 		
-		NodeDoubleClicked+=Lambda( node:TreeView.Node )
-			
-			node.Expanded=Not node.Expanded
-			
-			If node = RootNode And node.Expanded Then Refresh( True ) ' TRUE - need to refresh icons
-		End
+		NodeClicked+=OnNodeClicked
+		NodeRightClicked+=OnNodeRightClicked
+		NodeDoubleClicked+=OnNodeDoubleClicked
+		
+		NodeExpanded+=OnNodeExpanded
+		NodeCollapsed+=OnNodeCollapsed
 		
 		App.Activated+=Lambda()
-			New Fiber( Lambda()
-				Refresh()
-			End )
+		
+			UpdateAllNodes()
 		End
+		
+		UpdateFileTypeIcons()
+		
 	End
 	
-	Method Refresh( update:Bool=True )
-		
-		If update Then Update()
+	Method AddProject( dir:String )
 		
-		Local jarr:=GetFilterItems()
-		If Not jarr Return
+		Local node:=New Node( _rootNode )
+		Local s:=StripDir( dir )+" ("+dir+")"
+		node.Text=s
+		node._path=dir
+		UpdateProjIcon( node )
+		_expander.Restore( node )
 		
-		For Local i:=Eachin jarr
-			Local filter:=i.ToString()
-			Local type:=FilterType.Equals
-			If filter.StartsWith( "*" )
-				type|=FilterType.Starts
-				filter=filter.Slice( 1 )
-			Endif
-			If filter.EndsWith( "*" )
-				type|=FilterType.Ends
-				filter=filter.Slice( 0,filter.Length-1 )
+		UpdateNode( node )
+		ApplyFilter( node )
+	End
+	
+	Method RemoveProject( dir:String )
+	
+		Local s:=StripDir( dir )+" ("+dir+")"
+		Local toRemove:TreeView.Node=Null
+		For Local n:=Eachin RootNode.Children
+			If n.Text=s
+				toRemove=n
+				Exit
 			Endif
-			Filter( filter,type )
+		Next
+		If toRemove Then toRemove.Remove()
+	End
+	
+	Method UpdateAllNodes()
+	
+		Local selPath:=Selected ? GetNodePath( Selected ) Else ""
+	
+		For Local n:=Eachin _rootNode.Children
+			Local nn:=Cast<Node>( n )
+			_expander.Restore( nn )
+			UpdateNode( nn,True )
+			ApplyFilter( nn )
 		Next
 		
+		If selPath Then SelectByPath( selPath )
+	
+	End
+	
+	Method IsProjectNode:Bool( node:Node )
+		
+		Return node.Parent=_rootNode
+	End
+	
+	Method Refresh( node:Node )
+	
+		UpdateNode( node,True )
+	End
+	
+	Method Refresh( tnode:TreeView.Node )
+		
+		Local node:=Cast<Node>( tnode )
+		If node then UpdateNode( node,True )
 	End
 	
 	
 	Protected
 	
+	Class Node Extends TreeView.Node
+	
+		Method New( parent:Node )
+			Super.New( "",parent )
+		End
+	
+		Property Path:String()
+			Return _path
+		End
+		
+		Private
+	
+		Field _path:String
+	End
+	
+	Method GetFileTypeIcon:Image( path:String ) Virtual
+	
+		Local ext:=ExtractExt( path )
+		If Not ext Return Null
+	
+		Return _fileTypeIcons[ext.ToLower()]
+	End
+	
+	Method OnValidateStyle() Override
+	
+		Super.OnValidateStyle()
+		
+		UpdateFileTypeIcons()
+	
+		_dirIcon=_fileTypeIcons["._dir"]
+		_fileIcon=_fileTypeIcons["._file"]
+		
+		UpdateAllProjIcons()
+		
+		UpdateAllNodes()
+	End
+	
 	Method OnKeyEvent( event:KeyEvent ) Override
 		
 		If Selected And event.Type=EventType.KeyDown And event.Key=Key.KeyDelete
 			
-			Local node:=Cast<FileBrowserExt.Node>( Selected )
-			RequestedDelete( node.Path )
+			Local node:=Cast<Node>( Selected )
+			RequestedDelete( node )
 			event.Eat()
 			Return
 		Endif
@@ -74,88 +149,299 @@ Class ProjectBrowserView Extends FileBrowserExt
 	
 	Private
 	
-	Field _filters:Stack<JsonValue>
-	Field _fileTime:Long
+	Global _fileTypeIcons:StringMap<Image>
 	
-	Method Filter( filter:String,type:FilterType)
+	Field _rootNode:Node
+	Field _filters:=New StringMap<Stack<TextFilter>>
+	Field _filtersFileTimes:=New StringMap<Long>
 	
-		Local list:=RootNode.Children
-		If Not list Return
+	Field _dirIcon:Image
+	Field _fileIcon:Image
+	
+	
+	Method FindProjectNode:Node( node:TreeView.Node )
 		
-		For Local i:=Eachin list
-			FilterNode( i,filter,type )
-		Next
+		Local result:TreeView.Node
+		While node
+			result=node
+			node=node.Parent
+			If node=_rootNode Exit
+		Wend
+		Return result ? Cast<Node>( result ) Else Null
 	End
 	
-	Method FilterNode( node:TreeView.Node,filter:String,type:FilterType)
+	Method ApplyFilter( node:Node )
 		
-		Local ok:=False
-		Select type
-		Case FilterType.Equals
-			ok = (node.Text = filter)
-			
-		Case FilterType.Starts
-			ok = node.Text.StartsWith( filter )
-			
-		Case FilterType.Ends
-			ok = node.Text.EndsWith( filter )
-			
-		Case FilterType.Both
-			ok = node.Text.Find( filter )<>-1
-			
-		End
+		Local projNode:=FindProjectNode( node )
+		UpdateFilterItems( projNode )
+		
+		Local list:=_filters[projNode.Text]
+		If list And list.Length>0 And node.Expanded
+			For Local n:=Eachin node.Children
+				Filter( n,list )
+			Next
+		Endif
+	End
+	
+	Method Filtered:Bool( node:TreeView.Node,filters:Stack<TextFilter> )
+	
+		Local text:=node.Text
+		For Local f:=Eachin filters
+			If f.Filtered( text ) Return True
+		Next
+		Return False
+	End
+	
+	Method Filter( node:TreeView.Node,filters:Stack<TextFilter> )
 		
-		If ok
-			node.Parent.RemoveChild( node )
+		If Filtered( node,filters )
+			node.Remove()
 			Return
 		Endif
 		
-		Local list:=node.Children
-		If Not list Return
+		If Not node.Expanded Return
 		
-		For Local i:=Eachin list
-			FilterNode( i,filter,type )
+		For Local n:=Eachin node.Children
+			Filter( n,filters )
 		Next
 	End
 	
-	Method GetFilterItems:Stack<JsonValue>()
+	Method UpdateFilterItems( projNode:Node )
+		
+		Local path:=projNode.Path+"/project.json"
+		If GetFileType( path ) <> FileType.File Return
 		
-		Local path:=RootPath+"/project.json"
-		If GetFileType( path ) <> FileType.File Return _filters
+		Local projName:=projNode.Text
 		
 		Local t:=GetFileTime( path )
-		If t=_fileTime Return _filters
+		If t=_filtersFileTimes[projName] Return
 		
-		_fileTime=t
+		_filtersFileTimes[projName]=t
+		
+		Local list:=GetOrCreate( _filters,projName )
+		list.Clear()
 		
 		Local json:=JsonObject.Load( path )
 		If json.Contains( "exclude" )
-			_filters=json["exclude"].ToArray()
+			For Local i:=Eachin json["exclude"].ToArray()
+				Local f:=New TextFilter( i.ToString() )
+				list+=f
+			Next
 		Endif
+	End
+	
+	Method UpdateProjIcon( node:TreeView.Node )
+	
+		node.Icon = Prefs.MainProjectIcons ? ThemeImages.Get( "project/package.png" ) Else Null
+	End
+	
+	Method UpdateAllProjIcons()
 		
-		Return _filters
+		For Local n:=Eachin RootNode.Children
+			UpdateProjIcon( n )
+		Next
 	End
 	
-	Method OnThemeChanged() Override
+	Method UpdateNode( node:Node,recurse:Bool=True )
 		
-		Super.OnThemeChanged()
-		UpdateRootIcon()
-		Self.Refresh(True)
+		Local path:=node._path
+		'Print "update node: "+path
+		If Not path.EndsWith( "/" ) path+="/"
+		Local dir:=filesystem.LoadDir( path )
+	
+		Local dirs:=New Stack<String>
+		Local files:=New Stack<String>
+	
+		For Local f:=Eachin dir
+	
+			Local fpath:=path+f
+	
+			Select GetFileType( fpath )
+			Case FileType.Directory
+				dirs.Add( f )
+			Default
+				files.Add( f )
+			End
+		Next
+	
+		dirs.Sort()
+		files.Sort()
+	
+		Local i:=0,children:=node.Children
+	
+		While i<dir.Length
+	
+			Local f:=""
+			If i<dirs.Length f=dirs[i] Else f=files[i-dirs.Length]
+	
+			Local child:Node
+	
+			If i<children.Length
+				child=Cast<Node>( children[i] )
+				child.RemoveAllChildren()
+			Else
+				child=New Node( node )
+			Endif
+	
+			Local fpath:=path+f
+	
+			child.Text=f
+			child._path=fpath
+	
+			Local icon:Image
+			If Prefs.MainProjectIcons 'Only load icon if settings say so
+				icon=GetFileTypeIcon( fpath )
+			Endif
+	
+			If i<dirs.Length
+				If Not icon And Prefs.MainProjectIcons Then icon=_dirIcon
+				child.Icon=icon
+	
+				_expander.Restore( child )
+	
+				If child.Expanded Or recurse
+					UpdateNode( child,child.Expanded )
+				Endif
+			Else
+				If Not icon And Prefs.MainProjectIcons Then icon=_fileIcon
+				child.Icon=icon
+				child.RemoveAllChildren()
+			Endif
+	
+			i+=1
+		Wend
+	
+		node.RemoveChildren( i )
+	
 	End
 	
-	Method UpdateRootIcon()
-		If Prefs.MainProjectIcons Then 'Only load icon if settings say so
-			RootNode.Icon=ThemeImages.Get( "project/package.png" )
-		Else
-			RootNode.Icon=Null
-		Endif
+	Method OnNodeClicked( tnode:TreeView.Node )
+		
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileClicked( node )
+	End
+	
+	Method OnNodeRightClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileRightClicked( node )
 	End
 	
-	Enum FilterType
+	Method OnNodeDoubleClicked( tnode:TreeView.Node )
+		
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileDoubleClicked( node )
+	End
+	
+	Method OnNodeExpanded( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		UpdateNode( node,True )
+		ApplyFilter( node )
+	End
+	
+	Method OnNodeCollapsed( tnode:TreeView.Node )
+	
+	End
+	
+	Function UpdateFileTypeIcons()
+	
+		If _fileTypeIcons Return
+		
+		_fileTypeIcons=New StringMap<Image>
+		
+		Local dir:="theme::filetype_icons/"
+		
+		Local types:=stringio.LoadString( dir+"filetypes.txt" ).Split( "~n" )
+	
+		For Local type:=Eachin types
+		
+			type=type.Trim()
+			If Not type Continue
+			
+			Local icon:=Image.Load( dir+type )
+			If Not icon Continue
+			
+			icon.Scale=App.Theme.Scale
+			
+			_fileTypeIcons[ "."+StripExt(type) ]=icon
+		Next
+		
+		App.ThemeChanged+=Lambda()
+			For Local image:=Eachin _fileTypeIcons.Values
+				image.Scale=App.Theme.Scale
+			Next
+		End
+		
+	End
+	
+	
+End
+
+
+Class TextFilter
+	
+	Enum CheckType
 		Equals=0,
 		Starts=1,
 		Ends=2,
 		Both=3
 	End
 	
+	Method New( pattern:String )
+		
+		If pattern.StartsWith( "*" )
+			_type|=CheckType.Ends
+			pattern=pattern.Slice( 1 )
+		Endif
+		If pattern.EndsWith( "*" )
+			_type|=CheckType.Starts
+			pattern=pattern.Slice( 0,pattern.Length-1 )
+		Endif
+		_pattern=pattern
+	End
+	
+	Method Filtered:Bool( text:String )
+		
+		Local skip:=False
+		Select _type
+			
+			Case CheckType.Equals
+				skip = (text = _pattern)
+				
+			Case CheckType.Starts
+				skip = text.StartsWith( _pattern )
+				
+			Case CheckType.Ends
+				skip = text.EndsWith( _pattern )
+				
+			Case CheckType.Both
+				skip = (text.Find( _pattern )<>-1)
+				
+		End
+		
+		Return skip
+	End
+	
+	Property Pattern:String()
+		Return _pattern
+	End
+	
+	Property Type:CheckType()
+		Return _type
+	End
+	
+	Private
+	
+	Field _pattern:String
+	Field _type:=CheckType.Equals
+	
 End

+ 276 - 256
view/ProjectView.monkey2

@@ -26,23 +26,29 @@ Class ProjectView Extends ScrollView
 		openProject.HotKey=Key.O
 		openProject.HotKeyModifiers=Modifier.Menu|Modifier.Shift
 		openProject.Triggered=OnOpenProject
+		
+		InitProjBrowser()
 	End
 	
 	Property OpenProjects:String[]()
 	
-		Local projs:=New StringStack
-		For Local proj:=Eachin _projects.Keys
-			projs.Add( proj )
-		Next
+		Return _projects.ToArray()
+	End
+	
+	Property SingleClickExpanding:Bool()
+	
+		Return _projBrowser.SingleClickExpanding
+	
+	Setter( value:Bool )
 		
-		Return projs.ToArray()
+		_projBrowser.SingleClickExpanding=value
 	End
 	
 	Function FindProjectByFile:String( filePath:String )
 		
 		If Not filePath Return ""
 		
-		For Local p:=Eachin _projects.Keys
+		For Local p:=Eachin _projects
 			If filePath.StartsWith( p )
 				Return p
 			Endif
@@ -54,225 +60,13 @@ Class ProjectView Extends ScrollView
 	
 		dir=StripSlashes( dir )
 		
-		If _projects[dir] Return False
+		If _projects.Contains( dir ) Return False
 		
 		If GetFileType( dir )<>FileType.Directory Return False
-	
-		Local browser:=New ProjectBrowserView( dir )
-		
-		browser.RequestedDelete+=Lambda( path:String )
-		
-			DeleteItem( browser,path )
-		End
-		
-		If Prefs.SiblyMode
-		
-			browser.FileClicked+=Lambda( path:String )
-			
-				OnOpenDocument( path,False )
-			End
-		
-		Else 
-		
-			browser.FileDoubleClicked+=Lambda( path:String )
-			
-				OnOpenDocument( path )
-			End
-		
-		Endif
-		
-		browser.FileRightClicked+=Lambda( path:String )
-		
-			Local menu:=New MenuExt
-		
-			Select GetFileType( path )
-			Case FileType.Directory
-			
-				menu.AddAction( "Find..." ).Triggered=Lambda()
-				
-					RequestedFindInFolder( path )
-				End
-				
-				menu.AddSeparator()
-				
-				menu.AddAction( "New class..." ).Triggered=Lambda()
-				
-					Local d:=New GenerateClassDialog( path )
-					d.Generated+=Lambda( filePath:String,fileContent:String )
-						
-						If CreateFileInternal( filePath,fileContent )
-						
-							MainWindow.OpenDocument( filePath )
-							browser.Refresh()
-						Endif
-					End
-					d.ShowModal()
-				End
-				
-				menu.AddSeparator()
-				
-				menu.AddAction( "New file" ).Triggered=Lambda()
-				
-					Local file:=RequestString( "New file name:" )
-					If Not file Return
-					
-					Local tpath:=path+"/"+file
-					
-					CreateFileInternal( tpath )
-					
-					browser.Refresh()
-				End
-				
-				menu.AddAction( "New folder" ).Triggered=Lambda()
-				
-					Local dir:=RequestString( "New folder name:" )
-					If Not dir Return
-					
-					Local tpath:=path+"/"+dir
-					
-					If GetFileType( tpath )<>FileType.None
-						Alert( "A file or directory already exists at '"+tpath+"'" )
-						Return
-					End
-					
-					If Not CreateDir( tpath )
-						Alert( "Failed to create folder '"+dir+"'" )
-						Return
-					Endif
-					
-					browser.Refresh()
-				End
-				
-				menu.AddAction( "Delete" ).Triggered=Lambda()
-
-					DeleteItem( browser,path )
-				End
-				
-				menu.AddSeparator()
-				
-				If path = browser.RootPath ' root node
-					
-					menu.AddAction( "Close project" ).Triggered=Lambda()
-					
-						CloseProject( path )
-					End
-					
-					menu.AddAction( "Clean (delete .buildv)" ).Triggered=Lambda()
-						
-						If Not RequestOkay( "Really delete all '.buildv' folders?" ) Return
-						
-						Local changes:=CleanProject( path )
-						If changes Then browser.Refresh()
-					End
-				Else
-					
-					menu.AddAction( "Open as a project" ).Triggered=Lambda()
-					
-						OpenProject( path )
-					End
-				Endif
-				
-				' update / rebuild module
-				path=path.Replace( "\","/" )
-				Local name := path.Slice( path.FindLast( "/")+1 )
-				Local file:=path+"/module.json"
-				
-				If path.Contains( "/modules/") And GetFileType( file )=FileType.File
-					
-					menu.AddSeparator()
-					
-					menu.AddAction( "Update / Rebuild "+name ).Triggered=Lambda()
-						
-						_builder.BuildModules( True,name )
-					End
-					
-				Endif
-				
-				' update all modules
-				Local path2:=MainWindow.ModsPath
-				If path2.EndsWith( "/" ) Then path2=path2.Slice( 0,path2.Length-1 )
-				
-				If path = path2
-					
-					menu.AddSeparator()
-					
-					menu.AddAction( "Update / Rebuild modules" ).Triggered=Lambda()
-					
-						_builder.BuildModules( False )
-					End
-					
-				Endif
-				
-				' bananas showcase
-				If IsBananasShowcaseAvailable()
-					path2=Prefs.MonkeyRootPath+"bananas"
-					If path = path2
-					
-						menu.AddSeparator()
-					
-						menu.AddAction( "Open bananas showcase" ).Triggered=Lambda()
-					
-							MainWindow.ShowBananasShowcase()
-						End
-					
-					Endif
-				Endif
-				
-				menu.AddSeparator()
-				
-				menu.AddAction( "Open on Desktop" ).Triggered=Lambda()
-				
-					requesters.OpenUrl( path )
-				End
-				
-				
-			Case FileType.File
-			
-				menu.AddAction( "Open on Desktop" ).Triggered=Lambda()
-				
-					requesters.OpenUrl( path )
-				End
-				
-				menu.AddSeparator()
-			
-				menu.AddAction( "Rename" ).Triggered=Lambda()
-				
-					Local oldName:=StripDir( path )
-					Local name:=RequestString( "Enter new name:","Ranaming '"+oldName+"'",oldName )
-					If Not name Or name=oldName Return
-					
-					Local newPath:=ExtractDir( path )+name
-					If CopyFile( path,newPath )
-					
-						DeleteFile( path )
-					
-						browser.Refresh()
-						Return
-					Endif
-					
-					Alert( "Failed to rename file: '"+path+"'" )
-				End
-			
-				menu.AddSeparator()
-			
-				menu.AddAction( "Delete" ).Triggered=Lambda()
-					
-					DeleteItem( browser,path )
-				End
-				
-			Default
-			
-				Return
-			End
-			
-			menu.Open()
-		End
 		
-		_docker.AddView( browser,"top" )
-		
-		_projects[dir]=browser
+		_projects+=dir
 		
-		browser.Refresh()
+		_projBrowser.AddProject( dir )
 		
 		ProjectOpened( dir )
 
@@ -283,27 +77,38 @@ Class ProjectView Extends ScrollView
 
 		dir=StripSlashes( dir )
 		
-		Local view:=_projects[dir]
-		If Not view Return
-		
-		_docker.RemoveView( view )
+		_projBrowser.RemoveProject( dir )
 		
-		_projects.Remove( dir )
+		_projects-=dir
 		
 		ProjectClosed( dir )
 	End
 	
 	Method SaveState( jobj:JsonObject )
-	
+		
+		Local j:=New JsonObject
+		jobj["projectsExplorer"]=j
+		
 		Local jarr:=New JsonArray
-		For Local it:=Eachin _projects
-			jarr.Add( New JsonString( it.Key ) )
+		For Local p:=Eachin _projects
+			jarr.Add( New JsonString( p ) )
 		Next
-		jobj["openProjects"]=jarr
+		j["openProjects"]=jarr
+		
+		_projBrowser.SaveState( j,"expanded" )
+		
+		Local selPath:=GetNodePath( _projBrowser.Selected )
+		j["selected"]=New JsonString( selPath )
 	End
 	
 	Method LoadState( jobj:JsonObject )
-	
+		
+		If Not jobj.Contains( "projectsExplorer" ) Return
+		
+		jobj=new JsonObject( jobj["projectsExplorer"].ToObject() )
+		
+		_projBrowser.LoadState( jobj,"expanded" )
+		
 		If jobj.Contains( "openProjects" )
 			local arr:=jobj["openProjects"].ToArray()
 			For Local dir:=Eachin arr
@@ -311,6 +116,8 @@ Class ProjectView Extends ScrollView
 			Next
 		Endif
 		
+		Local selPath:=Json_GetString( jobj.Data,"selected","" )
+		If selPath Then _projBrowser.SelectByPath( selPath )
 	End
 	
 	
@@ -334,10 +141,13 @@ Class ProjectView Extends ScrollView
 	
 	Field _docs:DocumentManager
 	Field _docker:=New DockingView
-	Global _projects:=New StringMap<FileBrowserExt>
+	Global _projects:=New StringStack
 	Field _builder:IModuleBuilder
+	Field _projBrowser:ProjectBrowserView
 	
-	Method DeleteItem( browser:ProjectBrowserView,path:String )
+	Method DeleteItem( browser:ProjectBrowserView,path:String,node:TreeView.Node )
+		
+		Local nodeToRefresh:=Cast<ProjectBrowserView.Node>( node.Parent )
 		
 		Local work:=Lambda()
 			
@@ -346,7 +156,7 @@ Class ProjectView Extends ScrollView
 				If Not RequestOkay( "Really delete folder '"+path+"'?" ) Return
 				
 				If DeleteDir( path,True )
-					browser.Refresh()
+					browser.Refresh( nodeToRefresh )
 					Return
 				Endif
 				
@@ -362,7 +172,7 @@ Class ProjectView Extends ScrollView
 				
 					If doc doc.Close()
 				
-					browser.Refresh()
+					browser.Refresh( nodeToRefresh )
 					Return
 				Endif
 				
@@ -385,30 +195,28 @@ Class ProjectView Extends ScrollView
 	
 	Method OnOpenDocument( path:String,runExec:Bool=True )
 		
-		If GetFileType( path )=FileType.File
+		If GetFileType( path )<>FileType.File Return
 			
-			New Fiber( Lambda()
-				
-				Local ext:=ExtractExt( path )
-				Local exe:=(ext=".exe")
-				If runExec
-					If exe Or ext=".bat" Or ext=".sh"
-						Local s:="Do you want to execute this file?"
-						If Not exe s+="~nPress 'Cancel' to open file in editor."
-						If RequestOkay( s,StripDir( path ) )
-							OpenUrl( path )
-							Return
-						Endif
+		New Fiber( Lambda()
+			
+			Local ext:=ExtractExt( path )
+			Local exe:=(ext=".exe")
+			If runExec
+				If exe Or ext=".bat" Or ext=".sh"
+					Local s:="Do you want to execute this file?"
+					If Not exe s+="~nPress 'Cancel' to open file in editor."
+					If RequestOkay( s,StripDir( path ) )
+						OpenUrl( path )
+						Return
 					Endif
 				Endif
-				
-				If exe Return 'never open .exe
-				
-				_docs.OpenDocument( path,True )
-				
-			End )
-		
-		Endif
+			Endif
+			
+			If exe Return 'never open .exe
+			
+			_docs.OpenDocument( path,True )
+			
+		End )
 	End
 	
 	' Return True if there is an actual folder deletion
@@ -451,4 +259,216 @@ Class ProjectView Extends ScrollView
 		Return True
 	End
 	
+	Method InitProjBrowser()
+		
+		Local browser:=New ProjectBrowserView()
+		browser.SingleClickExpanding=Prefs.MainProjectSingleClickExpanding
+		_projBrowser=browser
+		_docker.AddView( browser,"top" )
+		
+		browser.RequestedDelete+=Lambda( node:ProjectBrowserView.Node )
+		
+			DeleteItem( browser,node.Path,node )
+		End
+		
+		browser.FileClicked+=Lambda( node:ProjectBrowserView.Node )
+			
+			If browser.SingleClickExpanding Then OnOpenDocument( node.Path )
+		End
+		
+		browser.FileDoubleClicked+=Lambda( node:ProjectBrowserView.Node )
+			
+			If Not browser.SingleClickExpanding Then OnOpenDocument( node.Path )
+		End
+		
+		browser.FileRightClicked+=Lambda( node:ProjectBrowserView.Node )
+		
+			Local menu:=New MenuExt
+			Local path:=node.Path
+		
+			Select GetFileType( path )
+			Case FileType.Directory
+		
+				menu.AddAction( "Find..." ).Triggered=Lambda()
+		
+					RequestedFindInFolder( path )
+				End
+		
+				menu.AddSeparator()
+		
+				menu.AddAction( "New class..." ).Triggered=Lambda()
+		
+					Local d:=New GenerateClassDialog( path )
+					d.Generated+=Lambda( filePath:String,fileContent:String )
+		
+						If CreateFileInternal( filePath,fileContent )
+		
+							MainWindow.OpenDocument( filePath )
+							browser.Refresh( node )
+						Endif
+					End
+					d.ShowModal()
+				End
+		
+				menu.AddSeparator()
+		
+				menu.AddAction( "New file" ).Triggered=Lambda()
+		
+					Local file:=RequestString( "New file name:" )
+					If Not file Return
+		
+					Local tpath:=path+"/"+file
+		
+					CreateFileInternal( tpath )
+		
+					browser.Refresh( node )
+				End
+		
+				menu.AddAction( "New folder" ).Triggered=Lambda()
+		
+					Local dir:=RequestString( "New folder name:" )
+					If Not dir Return
+		
+					Local tpath:=path+"/"+dir
+		
+					If GetFileType( tpath )<>FileType.None
+						Alert( "A file or directory already exists at '"+tpath+"'" )
+						Return
+					End
+		
+					If Not CreateDir( tpath )
+						Alert( "Failed to create folder '"+dir+"'" )
+						Return
+					Endif
+		
+					browser.Refresh( node )
+				End
+		
+				menu.AddAction( "Delete" ).Triggered=Lambda()
+		
+					DeleteItem( browser,path,node )
+				End
+		
+				menu.AddSeparator()
+		
+				If browser.IsProjectNode( node ) ' root node
+		
+					menu.AddAction( "Close project" ).Triggered=Lambda()
+		
+						CloseProject( path )
+					End
+		
+					menu.AddAction( "Clean (delete .buildv)" ).Triggered=Lambda()
+		
+						If Not RequestOkay( "Really delete all '.buildv' folders?" ) Return
+		
+						Local changes:=CleanProject( path )
+						If changes Then browser.Refresh( node )
+					End
+				Else
+		
+					menu.AddAction( "Open as a project" ).Triggered=Lambda()
+		
+						OpenProject( path )
+					End
+				Endif
+		
+				' update / rebuild module
+				path=path.Replace( "\","/" )
+				Local name := path.Slice( path.FindLast( "/")+1 )
+				Local file:=path+"/module.json"
+		
+				If path.Contains( "/modules/") And GetFileType( file )=FileType.File
+		
+					menu.AddSeparator()
+		
+					menu.AddAction( "Update / Rebuild "+name ).Triggered=Lambda()
+		
+						_builder.BuildModules( True,name )
+					End
+		
+				Endif
+		
+				' update all modules
+				Local path2:=MainWindow.ModsPath
+				If path2.EndsWith( "/" ) Then path2=path2.Slice( 0,path2.Length-1 )
+		
+				If path = path2
+		
+					menu.AddSeparator()
+		
+					menu.AddAction( "Update / Rebuild modules" ).Triggered=Lambda()
+		
+						_builder.BuildModules( False )
+					End
+		
+				Endif
+		
+				' bananas showcase
+				If IsBananasShowcaseAvailable()
+					path2=Prefs.MonkeyRootPath+"bananas"
+					If path = path2
+		
+						menu.AddSeparator()
+		
+						menu.AddAction( "Open bananas showcase" ).Triggered=Lambda()
+		
+							MainWindow.ShowBananasShowcase()
+						End
+		
+					Endif
+				Endif
+		
+				menu.AddSeparator()
+		
+				menu.AddAction( "Open on Desktop" ).Triggered=Lambda()
+		
+					requesters.OpenUrl( path )
+				End
+		
+		
+			Case FileType.File
+		
+				menu.AddAction( "Open on Desktop" ).Triggered=Lambda()
+		
+					requesters.OpenUrl( path )
+				End
+		
+				menu.AddSeparator()
+		
+				menu.AddAction( "Rename" ).Triggered=Lambda()
+		
+					Local oldName:=StripDir( path )
+					Local name:=RequestString( "Enter new name:","Ranaming '"+oldName+"'",oldName )
+					If Not name Or name=oldName Return
+		
+					Local newPath:=ExtractDir( path )+name
+					If CopyFile( path,newPath )
+		
+						DeleteFile( path )
+		
+						browser.Refresh( node.Parent )
+						Return
+					Endif
+		
+					Alert( "Failed to rename file: '"+path+"'" )
+				End
+		
+				menu.AddSeparator()
+		
+				menu.AddAction( "Delete" ).Triggered=Lambda()
+		
+					DeleteItem( browser,path,node )
+				End
+		
+			Default
+		
+				Return
+			End
+		
+			menu.Open()
+		End
+		
+	End
+	
 End

+ 16 - 8
view/TabViewExt.monkey2

@@ -189,21 +189,21 @@ Class TabViewExt Extends DockingView
 	
 	#rem monkeydoc Adds a tab.
 	#end	
-	Method AddTab:TabButtonExt( text:String,view:View,makeCurrent:Bool=False )
+	Method AddTab:TabButtonExt( text:String,view:View,makeCurrent:Bool=False,addAtBegin:Bool=False )
 	
-		Return AddTab( text,Null,view,makeCurrent )
+		Return AddTab( text,Null,view,makeCurrent,addAtBegin )
 	End
 
-	Method AddTab:TabButtonExt( text:String,icon:Image,view:View,makeCurrent:Bool=False )
+	Method AddTab:TabButtonExt( text:String,icon:Image,view:View,makeCurrent:Bool=False,addAtBegin:Bool=False )
 		
 		Local tab:=New TabButtonExt( text,icon,view,_flags & TabViewFlags.ClosableTabs,Self )
 		
-		AddTab( tab,makeCurrent )
+		AddTab( tab,makeCurrent,addAtBegin )
 		
 		Return tab
 	End
 	
-	Method AddTab( tab:TabButtonExt,makeCurrent:Bool=False )
+	Method AddTab( tab:TabButtonExt,makeCurrent:Bool=False,addAtBegin:Bool=False )
 	
 		Assert( TabIndex( tab.View )=-1,"View has already been added to TabView" )
 		
@@ -277,9 +277,17 @@ Class TabViewExt Extends DockingView
 		End
 		
 		Local index:=_tabs.Length
-
-		_tabBar.AddView( tab )
-		_tabs.Push( tab )
+		
+		If addAtBegin
+			_tabBar.RemoveAllViews()
+			_tabs.Insert( 0,tab )
+			For Local view:=Eachin _tabs
+				_tabBar.AddView( view )
+			Next
+		Else
+			_tabBar.AddView( tab )
+			_tabs.Add( tab )
+		Endif
 		
 		If makeCurrent MakeCurrent( tab,True ) 'CurrentIndex=index
 		

+ 199 - 23
view/TreeViewExt.monkey2

@@ -3,35 +3,67 @@ Namespace ted2go
 
 
 Class TreeViewExt Extends TreeView
-
-	Field SelectedChanged:Void( selNode:TreeView.Node )
+	
+	Field NodeClicked:Void( node:Node )
+	Field NodeRightClicked:Void( node:Node )
+	Field NodeDoubleClicked:Void( node:Node)
+	Field NodeExpanded:Void( node:Node )
+	Field NodeCollapsed:Void( node:Node )
+	Field SelectedChanged:Void( selNode:Node )
 	
 	Method New()
+		
 		Super.New()
 		
-		NodeClicked+=Lambda( node:TreeView.Node )
-			Selected=node
-			Self.MakeKeyView()
+		Super.NodeClicked+=Lambda( node:Node )
+			
+			If _singleClickExpanding
+				If TrySwitchExpandingState( node ) Return
+			Endif
+			
+			OnSelect( node )
+			NodeClicked( node )
+		End
+		
+		Super.NodeDoubleClicked+=Lambda( node:Node )
+			
+			If TrySwitchExpandingState( node ) Return
+			
+			OnSelect( node )
+			NodeDoubleClicked( node )
 		End
 		
-		NodeRightClicked+=Lambda( node:TreeView.Node )
-			Selected=node
-			Self.MakeKeyView()
+		Super.NodeRightClicked+=Lambda( node:Node )
+			
+			OnSelect( node )
+			NodeRightClicked( node )
 		End
 		
-		_selColor=App.Theme.GetColor( "panel" )
-		App.ThemeChanged+=Lambda()
-			_selColor=App.Theme.GetColor( "panel" )
+		Super.NodeExpanded+=Lambda( node:Node )
+			
+			_expander.Store( node )
+			OnSelect( node )
+			NodeExpanded( node )
+		End
+		
+		Super.NodeCollapsed+=Lambda( node:Node )
+			
+			_expander.Store( node )
+			OnSelect( node )
+			NodeCollapsed( node )
 		End
 		
+		_selColor=App.Theme.GetColor( "panel" )
 	End
 	
 	Property Selected:TreeView.Node()
+	
 		Return _sel
+		
 	Setter( value:TreeView.Node )
 		
-		If _sel = value Return
-		_sel = value
+		If _sel=value Return
+		_sel=value
 		SelectedChanged( _sel )
 		
 		EnsureVisible( _sel )
@@ -39,21 +71,47 @@ Class TreeViewExt Extends TreeView
 		RequestRender()
 	End
 	
-	Method FindSubNode:TreeView.Node( text:String,whereNode:TreeView.Node,recursive:Bool=False )
+	Property SingleClickExpanding:Bool()
+	
+		Return _singleClickExpanding
+	
+	Setter( value:Bool )
+	
+		_singleClickExpanding=value
 		
-		If whereNode.Text=text Return whereNode
+	End
+	
+	Method SaveState( jobj:JsonObject,jkey:String )
+		
+		_expander.SaveState( RootNode,jobj,jkey )
+	End
+	
+	Method LoadState( jobj:JsonObject,jkey:String )
 		
-		Local list:=whereNode.Children
-		If Not list Return Null
+		_expander.LoadState( jobj,jkey )
+	End
+	
+	Method FindSubNode:TreeView.Node( text:String,whereNode:TreeView.Node,recursive:Bool=False )
 		
-		For Local i:=Eachin list
-			If i.Text=text Return i
+		Return FindSubNode( whereNode,
+						recursive,
+						Lambda:Bool( n:TreeView.Node )
+							Return n.Text=text
+						End )
+	End
+	
+	Method FindSubNode:TreeView.Node( whereNode:TreeView.Node,recursive:Bool,findCondition:Bool(n:TreeView.Node) )
+	
+		If findCondition( whereNode ) Return whereNode
+	
+		For Local i:=Eachin whereNode.Children
+			If findCondition( i ) Return i
 			If recursive And i.Children
-				Local n:=FindSubNode( text,i,recursive )
+				Local n:=FindSubNode( i,recursive,findCondition )
 				If n Return n
 			Endif
 		Next
-		
+	
 		Return Null
 	End
 	
@@ -63,9 +121,27 @@ Class TreeViewExt Extends TreeView
 		If node=Selected Then Selected=Null
 	End
 	
+	Method SelectByPath( path:String )
+		
+		Local n:=FindSubNode( RootNode,
+						True,
+						Lambda:Bool( n:TreeView.Node )
+							Return GetNodePath( n )=path
+						End )
+		
+		If n Then Selected=n
+	End
+	
 	
 	Protected
 	
+	Field _expander:=New TreeViewExpander
+	
+	Method OnThemeChanged() Override
+		
+		_selColor=App.Theme.GetColor( "panel" )
+	End
+	
 	Method OnRenderContent( canvas:Canvas ) Override
 	
 		If _sel <> Null
@@ -101,12 +177,35 @@ Class TreeViewExt Extends TreeView
 	
 	Field _sel:TreeView.Node
 	Field _selColor:Color
+	Field _singleClickExpanding:Bool
+	
+	Method TrySwitchExpandingState:Bool( node:TreeView.Node )
+		
+		If node.Children.Length=0 Return False
+		
+		node.Expanded=Not node.Expanded
+		If node.Expanded
+			Super.NodeExpanded( node )
+		Else
+			Super.NodeCollapsed( node )
+		Endif
+		
+		Return True
+	End
+	
+	Method OnSelect( node:TreeView.Node )
+		
+		Selected=node
+		Self.MakeKeyView()
+		
+		RequestRender()
+	End
 	
 	Method EnsureVisible( node:TreeView.Node )
 		
 		If Not node Return
 		
-		Local n:=node
+		Local n:=node.Parent
 		While n
 			n.Expanded=True
 			n=n.Parent
@@ -123,6 +222,68 @@ Class TreeViewExt Extends TreeView
 End
 
 
+Class TreeViewExpander
+	
+	Method Store( node:TreeView.Node,recurse:Bool=False )
+	
+		Local key:=GetNodePath( node )
+	
+		If node.Expanded
+			_expands[key]=True
+		Else
+			_expands.Remove( key )
+		Endif
+		
+		If Not recurse Return
+		
+		For Local i:=Eachin node.Children
+			Store( i,True )
+		Next
+	End
+	
+	Method Restore( node:TreeView.Node,recurse:Bool=False )
+	
+		Local key:=GetNodePath( node )
+		
+		node.Expanded = _expands.Contains( key ) ? True Else False
+		
+		If Not recurse Return
+		
+		For Local i:=Eachin node.Children
+			Restore( i,True )
+		Next
+	End
+	
+	Method SaveState( rootNode:TreeView.Node,jobj:JsonObject,jkey:String )
+	
+		Store( rootNode,True ) '
+		
+		Local jarr:=New JsonArray
+		For Local key:=Eachin _expands.Keys
+			If Not key Continue
+			jarr.Add( New JsonString( key ) )
+		Next
+		jobj[jkey]=jarr
+	End
+	
+	Method LoadState( jobj:JsonObject,jkey:String )
+		
+		_expands.Clear()
+		
+		If Not jobj.Contains( jkey ) Return
+		Local jarr:=jobj[jkey].ToArray()
+		For Local key:=Eachin jarr
+			_expands[key.ToString()]=True
+		Next
+	End
+	
+	Private
+	
+	Field _expands:=New StringMap<Bool>
+	
+End
+
+
 Class NodeWithData<T> Extends TreeView.Node
 
 	Field data:T
@@ -135,11 +296,26 @@ Class NodeWithData<T> Extends TreeView.Node
 End
 
 
-Struct FileJumpData
+Class FileJumpData
 
 	Field path:String
 	Field pos:Int
 	Field len:Int
 	Field line:Int
+	Field posInLine:Int
+	
+End
+
+
+Function GetNodePath:String( node:TreeView.Node )
+	
+	If Not node Return ""
 	
+	Local s:=node.Text
+	Local i:=node.Parent
+	While i <> Null
+		s=i.Text+"\"+s
+		i=i.Parent
+	Wend
+	Return s
 End