1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
6
using System.Diagnostics;
8
using ICSharpCode.Core;
9
using ICSharpCode.NRefactory;
10
using ICSharpCode.SharpDevelop.Dom;
11
using ICSharpCode.SharpDevelop.Util;
13
namespace ICSharpCode.SharpDevelop.Editor
16
/// A permanent anchor that works even when a file is closed and later reopened.
18
public sealed class PermanentAnchor : ITextAnchor
20
IDocument currentDocument;
21
ITextAnchor baseAnchor;
27
AnchorMovementType movementType = AnchorMovementType.BeforeInsertion;
29
public PermanentAnchor(FileName fileName, int line, int column)
32
throw new ArgumentNullException("fileName");
34
throw new ArgumentOutOfRangeException("line");
36
throw new ArgumentOutOfRangeException("column");
38
Gui.WorkbenchSingleton.AssertMainThread();
40
this.fileName = fileName;
43
PermanentAnchorService.AddAnchor(this);
46
internal void AttachTo(IDocument document)
50
Debug.Assert(currentDocument == null && document != null);
51
this.currentDocument = document;
52
line = Math.Min(line, document.TotalNumberOfLines);
53
column = Math.Min(column, document.GetLine(line).Length + 1);
54
baseAnchor = document.CreateAnchor(document.PositionToOffset(line, column));
55
baseAnchor.MovementType = movementType;
56
baseAnchor.SurviveDeletion = surviveDeletion;
57
baseAnchor.Deleted += baseAnchor_Deleted;
60
internal void Detach()
64
Debug.Assert(currentDocument != null);
66
Location loc = baseAnchor.Location;
71
currentDocument = null;
74
internal void SetFileName(FileName newName)
76
this.fileName = newName;
79
internal void FileDeleted()
81
Debug.Assert(currentDocument == null);
82
if (!surviveDeletion) {
83
baseAnchor_Deleted(null, EventArgs.Empty);
87
void baseAnchor_Deleted(object sender, EventArgs e)
91
currentDocument = null;
98
/// Gets the file name of the anchor.
100
public FileName FileName {
101
get { return fileName; }
105
public event EventHandler Deleted;
108
public Location Location {
111
throw new InvalidOperationException();
112
if (baseAnchor != null)
113
return baseAnchor.Location;
115
return new Location(column, line);
120
/// Gets the editor to which this anchor currently belongs; or null if the file is not opened in any text editor.
122
public IDocument CurrentDocument {
123
get { return currentDocument; }
128
/// Warning: this method is only available while the document anchor is attached to a text editor, otherwise
129
/// it will throw an InvalidOperationException.
133
if (baseAnchor != null)
134
return baseAnchor.Offset;
136
throw new InvalidOperationException();
141
public AnchorMovementType MovementType {
142
get { return movementType; }
144
movementType = value;
145
if (baseAnchor != null)
146
baseAnchor.MovementType = value;
151
public bool SurviveDeletion {
152
get { return surviveDeletion; }
154
surviveDeletion = value;
155
if (baseAnchor != null)
156
baseAnchor.SurviveDeletion = value;
161
public bool IsDeleted {
162
get { return isDeleted; }
168
if (baseAnchor != null)
169
return baseAnchor.Line;
178
if (baseAnchor != null)
179
return baseAnchor.Column;
186
public static class PermanentAnchorService
188
static WeakCollection<PermanentAnchor> permanentAnchors = new WeakCollection<PermanentAnchor>();
189
static Dictionary<FileName, IDocument> openDocuments = new Dictionary<FileName, IDocument>();
191
internal static void AddAnchor(PermanentAnchor anchor)
193
permanentAnchors.Add(anchor);
196
if (openDocuments.TryGetValue(anchor.FileName, out doc))
197
anchor.AttachTo(doc);
201
/// Renames anchors without document.
203
internal static void FileRenamed(FileRenameEventArgs e)
205
FileName sourceFile = new FileName(e.SourceFile);
206
FileName targetFile = new FileName(e.TargetFile);
208
foreach (PermanentAnchor anchor in permanentAnchors) {
209
if (anchor.CurrentDocument == null) {
211
if (FileUtility.IsBaseDirectory(e.SourceFile, anchor.FileName)) {
212
anchor.SetFileName(new FileName(FileUtility.RenameBaseDirectory(anchor.FileName, e.SourceFile, e.TargetFile)));
215
if (anchor.FileName == sourceFile)
216
anchor.SetFileName(targetFile);
223
/// Deletes anchors without document.
225
internal static void FileDeleted(FileEventArgs e)
227
FileName fileName = new FileName(e.FileName);
228
foreach (PermanentAnchor anchor in permanentAnchors) {
229
if (anchor.CurrentDocument == null) {
231
if (FileUtility.IsBaseDirectory(fileName, anchor.FileName))
232
anchor.FileDeleted();
234
if (fileName == anchor.FileName)
235
anchor.FileDeleted();
242
/// Tells detached permanent anchors to attach to the specified text editor.
244
public static void AttachDocument(FileName fileName, IDocument document)
246
if (fileName == null)
247
throw new ArgumentNullException("fileName");
248
if (document == null)
249
throw new ArgumentNullException("document");
250
Gui.WorkbenchSingleton.AssertMainThread();
252
// there may be multiple documents with the same file name - in that case, only attach to one of them
253
if (!openDocuments.ContainsKey(fileName)) {
254
openDocuments.Add(fileName, document);
255
foreach (PermanentAnchor anchor in permanentAnchors) {
256
if (anchor.CurrentDocument == null && anchor.FileName == fileName) {
257
anchor.AttachTo(document);
264
/// Tells attached permanent anchors to detach from the specified text editor.
266
public static void DetachDocument(FileName fileName, IDocument document)
268
if (fileName == null)
269
throw new ArgumentNullException("fileName");
270
if (document == null)
271
throw new ArgumentNullException("document");
272
Gui.WorkbenchSingleton.AssertMainThread();
274
IDocument actualDocument;
275
if (openDocuments.TryGetValue(fileName, out actualDocument)) {
276
// test whether we're detaching the correct document
277
if (document == actualDocument) {
278
openDocuments.Remove(fileName);
279
foreach (PermanentAnchor anchor in permanentAnchors) {
280
if (anchor.CurrentDocument == document) {
289
/// Informs the PermanentAnchorService when the file name of a document has changed.
291
public static void RenameDocument(FileName oldFileName, FileName newFileName, IDocument document)
293
if (oldFileName == null)
294
throw new ArgumentNullException("oldFileName");
295
if (newFileName == null)
296
throw new ArgumentNullException("newFileName");
297
if (document == null)
298
throw new ArgumentNullException("document");
299
Gui.WorkbenchSingleton.AssertMainThread();
301
IDocument actualDocument;
302
if (openDocuments.TryGetValue(oldFileName, out actualDocument)) {
303
// test whether we're detaching the correct document
304
if (document == actualDocument) {
305
if (openDocuments.ContainsKey(newFileName)) {
306
// new file name already taken? just detach the old stuff
307
DetachDocument(oldFileName, document);
309
openDocuments.Remove(oldFileName);
310
openDocuments.Add(newFileName, document);
311
foreach (PermanentAnchor anchor in permanentAnchors) {
312
if (anchor.CurrentDocument == document) {
313
anchor.SetFileName(newFileName);