2
// XcodeProjectTracker.cs
5
// Michael Hutchinson <mhutchinson@novell.com>
7
// Copyright (c) 2011 Novell, Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
using System.Diagnostics;
32
using System.Collections.Generic;
34
using MonoDevelop.Core;
35
using MonoDevelop.Projects;
36
using MonoDevelop.MacDev.ObjCIntegration;
37
using System.Threading.Tasks;
38
using MonoDevelop.MacDev.XcodeIntegration;
39
using MonoDevelop.MacInterop;
41
namespace MonoDevelop.MacDev.XcodeSyncing
43
public interface IXcodeTrackedProject
45
XcodeProjectTracker XcodeProjectTracker { get; }
48
public abstract class XcodeProjectTracker : IDisposable
50
readonly NSObjectInfoService infoService;
51
readonly DotNetProject dnp;
53
object xcode_lock = new object ();
54
List<NSObjectTypeInfo> userTypes;
57
bool updatingProjectFiles;
61
get { return dnp.BaseDirectory.Combine ("obj"); }
65
get { return ObjDir.Combine ("Xcode"); }
68
public XcodeProjectTracker (DotNetProject dnp, NSObjectInfoService infoService)
71
throw new ArgumentNullException ("dnp");
73
this.infoService = infoService;
74
AppleSdkSettings.Changed += DisableSyncing;
77
public bool ShouldOpenInXcode (FilePath fileName)
79
if (!HasInterfaceDefinitionExtension (fileName))
82
var file = dnp.Files.GetFile (fileName);
83
return file != null && (file.BuildAction == BuildAction.InterfaceDefinition);
86
public virtual bool HasInterfaceDefinitionExtension (FilePath fileName)
88
return fileName.HasExtension (".xib");
91
protected virtual string[] GetFrameworks ()
93
return new string[] { "Foundation" };
97
get { return xcode != null; }
100
void EnableSyncing (IProgressMonitor monitor)
105
monitor.Log.WriteLine ("Enabled syncing for project: {0}", dnp.Name);
107
xcode = new XcodeMonitor (XcodeDir, dnp.Name);
109
dnp.FileAddedToProject += FileAddedToProject;
110
dnp.FilePropertyChangedInProject += FilePropertyChangedInProject;
111
dnp.FileRemovedFromProject += FileRemovedFromProject;
112
dnp.FileChangedInProject += FileChangedInProject;
113
dnp.NameChanged += ProjectNameChanged;
114
MonoDevelop.Ide.IdeApp.CommandService.ApplicationFocusIn += AppRegainedFocus;
117
void DisableSyncing ()
119
DisableSyncing (true);
122
void DisableSyncing (bool closeProject)
127
XC4Debug.Log ("Disabled syncing for project: {0}", dnp.Name);
132
xcode.CloseProject ();
133
xcode.DeleteProjectDirectory ();
135
XC4Debug.Unindent ();
139
dnp.FileAddedToProject -= FileAddedToProject;
140
dnp.FilePropertyChangedInProject -= FilePropertyChangedInProject;;
141
dnp.FileRemovedFromProject -= FileRemovedFromProject;
142
dnp.FileChangedInProject -= FileChangedInProject;
143
dnp.NameChanged -= ProjectNameChanged;
144
MonoDevelop.Ide.IdeApp.CommandService.ApplicationFocusIn -= AppRegainedFocus;
147
void ShowXcodeScriptError ()
149
MonoDevelop.Ide.MessageService.ShowError (
150
GettextCatalog.GetString ("MonoDevelop could not communicate with Xcode"),
151
GettextCatalog.GetString ("If Xcode is still running, please ensure that all changes have been saved and " +
152
"Xcode has been exited before continuing, otherwise any new changes may be lost."));
155
void AppRegainedFocus (object sender, EventArgs e)
161
XC4Debug.Log ("MonoDevelop has regained focus.");
163
using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Synchronizing changes from Xcode..."))) {
164
bool projectOpen = false;
167
// Note: Both IsProjectOpen() and SaveProject() may throw TimeoutExceptions or AppleScriptExceptions
168
if ((projectOpen = xcode.IsProjectOpen ()))
169
xcode.SaveProject (monitor);
170
} catch (Exception ex) {
171
ShowXcodeScriptError ();
172
monitor.Log.WriteLine ("Xcode failed to save pending changes to project: {0}", ex);
174
// Note: This will cause us to disable syncing after we sync whatever we can over from Xcode...
179
SyncXcodeChanges (monitor);
182
XC4Debug.Log ("Xcode project for '{0}' is not open, disabling syncing.", dnp.Name);
183
DisableSyncing (false);
190
bool OpenFileInXcodeProject (IProgressMonitor monitor, string path)
192
bool succeeded = false;
195
EnableSyncing (monitor);
197
if (!UpdateTypes (monitor) || monitor.IsCancelRequested)
200
if (!UpdateXcodeProject (monitor) || monitor.IsCancelRequested)
203
xcode.OpenFile (monitor, path);
205
} catch (AppleScriptException asex) {
206
ShowXcodeScriptError ();
207
monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), asex);
208
} catch (TimeoutException tex) {
209
ShowXcodeScriptError ();
210
monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), tex);
211
} catch (Exception ex) {
212
monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), ex);
215
DisableSyncing (true);
221
public bool OpenDocument (string path)
223
var xibFile = dnp.Files.GetFile (path);
224
bool success = false;
226
Debug.Assert (xibFile != null);
227
Debug.Assert (IsInterfaceDefinition (xibFile));
230
using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Opening document '{0}' from project '{1}' in Xcode...", xibFile.ProjectVirtualPath, dnp.Name))) {
231
monitor.BeginTask (GettextCatalog.GetString ("Opening document '{0}' from project '{1}' in Xcode...", xibFile.ProjectVirtualPath, dnp.Name), 0);
232
success = OpenFileInXcodeProject (monitor, xibFile.ProjectVirtualPath);
240
static bool IsInterfaceDefinition (ProjectFile pf)
242
return pf.BuildAction == BuildAction.InterfaceDefinition;
245
bool IncludeInSyncedProject (ProjectFile pf)
247
return (pf.BuildAction == BuildAction.Content && pf.ProjectVirtualPath.ParentDirectory.IsNullOrEmpty)
248
|| (pf.BuildAction == BuildAction.InterfaceDefinition && HasInterfaceDefinitionExtension (pf.FilePath));
251
#region Project change tracking
253
void ProjectNameChanged (object sender, SolutionItemRenamedEventArgs e)
259
XC4Debug.Log ("Project '{0}' was renamed to '{1}', resetting Xcode sync.", e.OldName, e.NewName);
263
xcode.CloseProject ();
264
xcode.DeleteProjectDirectory ();
266
XC4Debug.Unindent ();
269
xcode = new XcodeMonitor (dnp.BaseDirectory.Combine ("obj", "Xcode"), dnp.Name);
273
void FileRemovedFromProject (object sender, ProjectFileEventArgs e)
279
XC4Debug.Log ("Files removed from project '{0}'", dnp.Name);
280
foreach (var file in e)
281
XC4Debug.Log (" * Removed: {0}", file.ProjectFile.ProjectVirtualPath);
285
if (e.Any (finf => finf.Project == dnp && IsInterfaceDefinition (finf.ProjectFile))) {
286
if (!dnp.Files.Any (IsInterfaceDefinition)) {
287
XC4Debug.Log ("Last Interface Definition file removed from '{0}', disabling Xcode sync.", dnp.Name);
288
DisableSyncing (true);
293
XC4Debug.Unindent ();
296
CheckFileChanges (e);
300
void FileAddedToProject (object sender, ProjectFileEventArgs e)
306
XC4Debug.Log ("Files added to project '{0}'", dnp.Name);
307
foreach (var file in e)
308
XC4Debug.Log (" * Added: {0}", file.ProjectFile.ProjectVirtualPath);
310
CheckFileChanges (e);
314
void FileChangedInProject (object sender, ProjectFileEventArgs e)
317
// avoid infinite recursion when we add files
318
if (!SyncingEnabled || updatingProjectFiles)
321
XC4Debug.Log ("Files changed in project '{0}'", dnp.Name);
322
foreach (var file in e)
323
XC4Debug.Log (" * Changed: {0}", file.ProjectFile.ProjectVirtualPath);
325
CheckFileChanges (e);
329
void FilePropertyChangedInProject (object sender, ProjectFileEventArgs e)
335
XC4Debug.Log ("File properties changed in project '{0}'", dnp.Name);
336
foreach (var file in e)
337
XC4Debug.Log (" * Property Changed: {0}", file.ProjectFile.ProjectVirtualPath);
339
CheckFileChanges (e);
343
void CheckFileChanges (ProjectFileEventArgs e)
345
bool updateTypes = false, updateProject = false;
347
foreach (ProjectFileEventInfo finfo in e) {
348
if (finfo.Project != dnp)
351
if (finfo.ProjectFile.BuildAction == BuildAction.Compile) {
353
} else if (IncludeInSyncedProject (finfo.ProjectFile)) {
354
updateProject = true;
359
using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Syncing types to Xcode..."))) {
360
//FIXME: make this async (and safely async)
361
//FIXME: only update the project if obj-c types change
362
updateProject |= UpdateTypes (monitor);
367
using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Syncing project to Xcode..."))) {
368
//FIXME: make this async (and safely async)
369
var running = xcode.CheckRunning ();
370
UpdateXcodeProject (monitor);
373
xcode.OpenProject (monitor);
374
} catch (AppleScriptException) {
375
ShowXcodeScriptError ();
376
} catch (TimeoutException) {
377
ShowXcodeScriptError ();
386
#region Progress monitors
388
//FIXME: should we use a modal monitor to prevent the user doing unexpected things?
389
IProgressMonitor GetStatusMonitor (string title)
391
IProgressMonitor monitor = MonoDevelop.Ide.IdeApp.Workbench.ProgressMonitors.GetStatusProgressMonitor (
394
monitor = new MonoDevelop.Core.ProgressMonitoring.AggregatedProgressMonitor (
395
monitor, XC4Debug.GetLoggingMonitor ());
402
#region Outbound syncing
404
bool UpdateTypes (IProgressMonitor monitor)
406
monitor.BeginTask (GettextCatalog.GetString ("Updating Objective-C type information"), 0);
408
var pinfo = infoService.GetProjectInfo (dnp);
411
// FIXME: report progress
413
userTypes = pinfo.GetTypes ().Where (t => t.IsUserType).ToList ();
415
} catch (Exception ex) {
416
monitor.ReportError (GettextCatalog.GetString ("Error updating Objective-C type information"), ex);
423
protected abstract XcodeProject CreateProject (string name);
425
bool UpdateXcodeProject (IProgressMonitor monitor)
428
// Ensure that the obj directory and all subfiles/subdirectories
429
// are writeable so we can create the temp files for xcode syncing
430
dnp.BaseDirectory.MakeWritable ();
431
ObjDir.MakeWritable ();
432
XcodeDir.MakeWritable (true);
434
xcode.UpdateProject (monitor, CreateSyncList (), CreateProject (dnp.Name));
436
} catch (AppleScriptException asex) {
437
ShowXcodeScriptError ();
438
monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), asex);
440
} catch (TimeoutException tex) {
441
ShowXcodeScriptError ();
442
monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), tex);
444
} catch (Exception ex) {
445
monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), ex);
450
List<XcodeSyncedItem> CreateSyncList ()
452
var syncList = new List<XcodeSyncedItem> ();
453
foreach (var file in dnp.Files.Where (IncludeInSyncedProject))
454
syncList.Add (new XcodeSyncedContent (file));
455
foreach (var type in userTypes)
456
syncList.Add (new XcodeSyncedType (type, GetFrameworks ()));
463
#region Inbound syncing
465
bool SyncXcodeChanges (IProgressMonitor monitor)
468
monitor.BeginTask (GettextCatalog.GetString ("Detecting changed files in Xcode..."), 0);
469
var changeCtx = xcode.GetChanges (monitor, infoService, dnp);
472
updatingProjectFiles = true;
473
bool filesAdded = false;
474
bool typesAdded = false;
476
// First, copy any changed/added resource files to MonoDevelop's project directory.
477
CopyFilesToMD (monitor, changeCtx);
479
// Then update CLI types.
480
if (UpdateCliTypes (monitor, changeCtx, out typesAdded))
483
// Next, parse UI definition files for custom classes
484
if (AddCustomClassesFromUIDefinitionFiles (monitor, changeCtx))
487
// Finally, add any newly created resource files to the DotNetProject.
488
if (AddFilesToMD (monitor, changeCtx))
491
// Save the DotNetProject.
492
if (filesAdded || typesAdded)
493
Ide.IdeApp.ProjectOperations.Save (dnp);
495
// Notify MonoDevelop of file changes.
496
Gtk.Application.Invoke (delegate {
497
// FIXME: this should probably filter out any IsFreshlyAdded file jobs
498
FileService.NotifyFilesChanged (changeCtx.FileSyncJobs.Select (f => f.Original));
501
if (typesAdded && xcode.CheckRunning ())
502
UpdateXcodeProject (monitor);
505
} catch (Exception ex) {
506
monitor.ReportError (GettextCatalog.GetString ("Error synchronizing changes from Xcode"), ex);
509
updatingProjectFiles = false;
514
/// Copies resource files from the Xcode project (back) to the MonoDevelop project directory.
516
/// <param name='monitor'>
517
/// A progress monitor.
519
/// <param name='context'>
520
/// The sync context.
522
void CopyFilesToMD (IProgressMonitor monitor, XcodeSyncBackContext context)
524
if (context.FileSyncJobs.Count == 0)
527
monitor.BeginStepTask ("Copying files from Xcode back to MonoDevelop...", context.FileSyncJobs.Count, 1);
529
foreach (var file in context.FileSyncJobs) {
530
monitor.Log.WriteLine ("Copying {0} file from Xcode: {1}", file.IsFreshlyAdded ? "new" : "changed", file.SyncedRelative);
532
if (!Directory.Exists (file.Original.ParentDirectory))
533
Directory.CreateDirectory (file.Original.ParentDirectory);
535
var tempFile = file.Original.ParentDirectory.Combine (".#" + file.Original.ParentDirectory.FileName);
536
FilePath path = context.ProjectDir.Combine (file.SyncedRelative);
538
if (File.Exists (path)) {
539
File.Copy (path, tempFile);
540
FileService.SystemRename (tempFile, file.Original);
542
DateTime mtime = File.GetLastWriteTime (file.Original);
543
context.SetSyncTime (file.SyncedRelative, mtime);
545
monitor.ReportWarning (string.Format ("'{0}' does not exist.", file.SyncedRelative));
555
/// Adds any newly created resource files to MonoDevelop's DotNetProject.
557
/// <param name='monitor'>
558
/// A progress monitor.
560
/// <param name='context'>
561
/// The sync context.
564
/// Returns whether or not new files were added to the project.
566
bool AddFilesToMD (IProgressMonitor monitor, XcodeSyncBackContext context)
568
bool filesAdded = false;
570
if (context.FileSyncJobs.Count == 0)
573
foreach (var file in context.FileSyncJobs) {
574
if (!file.IsFreshlyAdded)
577
monitor.Log.WriteLine ("Adding '{0}' to project '{1}'", file.SyncedRelative, dnp.Name);
579
FilePath path = new FilePath (file.Original);
580
string buildAction = HasInterfaceDefinitionExtension (path) ? BuildAction.InterfaceDefinition : BuildAction.Content;
581
context.Project.AddFile (path, buildAction);
588
protected virtual IEnumerable<NSObjectTypeInfo> GetCustomTypesFromUIDefinition (FilePath fileName)
594
/// Adds the custom classes from user interface definition files.
597
/// <c>true</c> if new types were added to the project, or <c>false</c> otherwise.
599
/// <param name='monitor'>
600
/// A progress monitor.
602
/// <param name='context'>
603
/// A sync-back context.
605
bool AddCustomClassesFromUIDefinitionFiles (IProgressMonitor monitor, XcodeSyncBackContext context)
607
var provider = dnp.LanguageBinding.GetCodeDomProvider ();
608
var options = new System.CodeDom.Compiler.CodeGeneratorOptions ();
609
var writer = MonoDevelop.DesignerSupport.CodeBehindWriter.CreateForProject (
610
new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor (), dnp);
611
bool addedTypes = false;
612
bool beganTask = false;
614
// Collect our list of custom classes from UI definition files
615
foreach (var job in context.FileSyncJobs) {
616
if (!HasInterfaceDefinitionExtension (job.Original))
620
monitor.BeginTask (GettextCatalog.GetString ("Generating custom classes defined in UI definition files..."), 0);
624
string relative = job.SyncedRelative.ParentDirectory;
625
string dir = dnp.BaseDirectory;
627
if (!string.IsNullOrEmpty (relative))
628
dir = Path.Combine (dir, relative);
630
foreach (var type in GetCustomTypesFromUIDefinition (job.Original)) {
631
if (context.ProjectInfo.ContainsType (type.ObjCName))
634
string designerPath = Path.Combine (dir, type.ObjCName + ".designer." + provider.FileExtension);
635
string path = Path.Combine (dir, type.ObjCName + "." + provider.FileExtension);
636
string ns = dnp.GetDefaultNamespace (path);
638
type.CliName = ns + "." + provider.CreateValidIdentifier (type.ObjCName);
640
if (provider is Microsoft.CSharp.CSharpCodeProvider) {
641
CodebehindTemplateBase cs = new CSharpCodeTypeDefinition () {
642
WrapperNamespace = infoService.WrapperRoot,
647
writer.WriteFile (path, cs.TransformText ());
649
List<NSObjectTypeInfo> types = new List<NSObjectTypeInfo> ();
652
cs = new CSharpCodeCodebehind () {
653
WrapperNamespace = infoService.WrapperRoot,
658
writer.WriteFile (designerPath, cs.TransformText ());
660
context.ProjectInfo.InsertUpdatedType (type);
662
// FIXME: implement support for non-C# languages
665
dnp.AddFile (new ProjectFile (path));
666
dnp.AddFile (new ProjectFile (designerPath) { DependsOn = path });
671
writer.WriteOpenFiles ();
680
/// Updates the cli types.
683
/// Returns whether or not any files were added to the project.
685
/// <param name='monitor'>
686
/// A progress monitor.
688
/// <param name='context'>
689
/// A sync-back context.
691
/// <param name='typesAdded'>
692
/// An output variable specifying whether or not any types were added to the project.
694
bool UpdateCliTypes (IProgressMonitor monitor, XcodeSyncBackContext context, out bool typesAdded)
696
var provider = dnp.LanguageBinding.GetCodeDomProvider ();
697
var options = new System.CodeDom.Compiler.CodeGeneratorOptions ();
698
var writer = MonoDevelop.DesignerSupport.CodeBehindWriter.CreateForProject (
699
new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor (), dnp);
701
monitor.BeginTask (GettextCatalog.GetString ("Detecting changes made in Xcode to custom user types..."), 0);
702
Dictionary<string, NSObjectTypeInfo> newTypes;
703
Dictionary<string, ProjectFile> newFiles;
704
var updates = context.GetTypeUpdates (monitor, provider, out newTypes, out newFiles);
705
if ((updates == null || updates.Count == 0) && newTypes == null && newFiles == null) {
706
monitor.Log.WriteLine ("No changes found.");
713
int count = updates.Count + (newTypes != null ? newTypes.Count : 0);
714
monitor.BeginTask (GettextCatalog.GetString ("Updating custom user types in MonoDevelop..."), count);
716
// First, add new types...
717
if (newTypes != null && newTypes.Count > 0) {
718
foreach (var nt in newTypes) {
719
monitor.Log.WriteLine ("Adding new type: {0}", nt.Value.CliName);
721
if (provider is Microsoft.CSharp.CSharpCodeProvider) {
722
var cs = new CSharpCodeTypeDefinition () {
723
WrapperNamespace = infoService.WrapperRoot,
728
string baseDir = Path.GetDirectoryName (nt.Key);
729
if (!Directory.Exists (baseDir))
730
Directory.CreateDirectory (baseDir);
732
writer.WriteFile (nt.Key, cs.TransformText ());
734
// FIXME: implement support for non-C# languages
745
// Next, generate the designer files for any added/changed types
746
foreach (var df in updates) {
747
monitor.Log.WriteLine ("Generating designer file: {0}", df.Key);
748
if (provider is Microsoft.CSharp.CSharpCodeProvider) {
749
var cs = new CSharpCodeCodebehind () {
751
WrapperNamespace = infoService.WrapperRoot,
754
writer.WriteFile (df.Key, cs.TransformText ());
756
var ccu = GenerateCompileUnit (provider, options, df.Key, df.Value);
757
writer.WriteFile (df.Key, ccu);
763
writer.WriteOpenFiles ();
765
// Update sync timestamps
766
foreach (var df in updates) {
767
foreach (var type in df.Value)
768
context.SetSyncTime (type.ObjCName + ".h", DateTime.Now);
771
// Add new files to the DotNetProject
772
if (newFiles != null) {
773
foreach (var f in newFiles) {
774
monitor.Log.WriteLine ("Added new designer file: {0}", f.Key);
775
dnp.AddFile (f.Value);
781
return newFiles != null && newFiles.Count > 0;
784
System.CodeDom.CodeCompileUnit GenerateCompileUnit (System.CodeDom.Compiler.CodeDomProvider provider,
785
System.CodeDom.Compiler.CodeGeneratorOptions options, string file, List<NSObjectTypeInfo> types)
787
var ccu = new System.CodeDom.CodeCompileUnit ();
788
var namespaces = new Dictionary<string, System.CodeDom.CodeNamespace> ();
789
foreach (var t in types) {
790
System.CodeDom.CodeTypeDeclaration type;
792
System.CodeDom.CodeNamespace ns;
793
t.GenerateCodeTypeDeclaration (provider, options, infoService.WrapperRoot, out type, out nsName);
794
if (!namespaces.TryGetValue (nsName, out ns)) {
795
namespaces[nsName] = ns = new System.CodeDom.CodeNamespace (nsName);
796
ccu.Namespaces.Add (ns);
805
public void Dispose ()
811
DisableSyncing (true);
812
AppleSdkSettings.Changed -= DisableSyncing;
816
static class XC4Debug
818
static int indentLevel = 0;
819
static TextWriter writer;
823
FilePath logDir = UserProfile.Current.LogDir;
824
FilePath logFile = logDir.Combine (UniqueLogFile);
826
FileService.EnsureDirectoryExists (logDir);
829
var stream = File.Open (logFile, FileMode.Create, FileAccess.Write, FileShare.Read);
830
writer = new StreamWriter (stream) { AutoFlush = true };
831
} catch (Exception ex) {
832
LoggingService.LogError ("Could not create Xcode sync logging file", ex);
836
static string UniqueLogFile {
838
return string.Format ("Xcode4Sync-{0}.log", LoggingService.LogTimestamp.ToString ("yyyy-MM-dd__HH-mm-ss"));
842
static string TimeStamp {
843
get { return string.Format ("[{0}] ", DateTime.Now.ToString ("yyyy-MM-dd HH:mm:ss.f")); }
846
public static void Indent ()
851
public static void Unindent ()
856
public static void Log (string message)
861
writer.WriteLine (TimeStamp + new string (' ', indentLevel * 3) + message);
864
public static void Log (string format, params object[] args)
869
writer.WriteLine (TimeStamp + new string (' ', indentLevel * 3) + format, args);
872
public static IProgressMonitor GetLoggingMonitor ()
875
return new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor ();
877
return new MonoDevelop.Core.ProgressMonitoring.ConsoleProgressMonitor (writer) { EnableTimeStamp = true, WrapText = false };