You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
310 lines
10 KiB
310 lines
10 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.ComponentModel.Design; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Windows.Threading; |
|
|
|
using ICSharpCode.AvalonEdit.AddIn.Options; |
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
using ICSharpCode.AvalonEdit.Utils; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.NRefactory.Editor; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Editor.Bookmarks; |
|
using ICSharpCode.SharpDevelop.Editor; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.SharpDevelop.Parser; |
|
using ICSharpCode.SharpDevelop.Project; |
|
using ICSharpCode.SharpDevelop.Workbench; |
|
|
|
namespace ICSharpCode.AvalonEdit.AddIn |
|
{ |
|
public class AvalonEditViewContent |
|
: AbstractViewContent, IMementoCapable, IToolsHost |
|
{ |
|
readonly CodeEditor codeEditor = new CodeEditor(); |
|
IAnalyticsMonitorTrackedFeature trackedFeature; |
|
|
|
public AvalonEditViewContent(OpenedFile file, Encoding fixedEncodingForLoading = null) |
|
{ |
|
// Use common service container for view content and primary text editor. |
|
// This makes all text editor services available as view content services and vice versa. |
|
// (with the exception of the interfaces implemented directly by this class, |
|
// those are available as view-content services only) |
|
this.Services = codeEditor.PrimaryTextEditor.GetRequiredService<IServiceContainer>(); |
|
if (fixedEncodingForLoading != null) { |
|
codeEditor.UseFixedEncoding = true; |
|
codeEditor.PrimaryTextEditor.Encoding = fixedEncodingForLoading; |
|
} |
|
this.TabPageText = "${res:FormsDesigner.DesignTabPages.SourceTabPage}"; |
|
|
|
if (file.FileName != null) { |
|
string filetype = Path.GetExtension(file.FileName); |
|
if (!IsKnownFileExtension(filetype)) |
|
filetype = ".?"; |
|
trackedFeature = SD.AnalyticsMonitor.TrackFeature(typeof(AvalonEditViewContent), "open" + filetype.ToLowerInvariant()); |
|
} |
|
|
|
this.Files.Add(file); |
|
file.ForceInitializeView(this); |
|
|
|
file.IsDirtyChanged += PrimaryFile_IsDirtyChanged; |
|
codeEditor.Document.UndoStack.PropertyChanged += codeEditor_Document_UndoStack_PropertyChanged; |
|
codeEditor.CaretPositionChanged += CaretChanged; |
|
codeEditor.TextCopied += codeEditor_TextCopied; |
|
} |
|
|
|
bool IsKnownFileExtension(string filetype) |
|
{ |
|
return ProjectService.GetFileFilters().Any(f => f.ContainsExtension(filetype)) || |
|
IconService.HasImageForFile(filetype); |
|
} |
|
|
|
void codeEditor_Document_UndoStack_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) |
|
{ |
|
if (!isLoading) |
|
PrimaryFile.IsDirty = !codeEditor.Document.UndoStack.IsOriginalFile; |
|
} |
|
|
|
void PrimaryFile_IsDirtyChanged(object sender, EventArgs e) |
|
{ |
|
var document = codeEditor.Document; |
|
if (document != null) { |
|
var undoStack = document.UndoStack; |
|
if (this.PrimaryFile.IsDirty) { |
|
if (undoStack.IsOriginalFile) |
|
undoStack.DiscardOriginalFileMarker(); |
|
} else { |
|
undoStack.MarkAsOriginalFile(); |
|
} |
|
} |
|
} |
|
|
|
void codeEditor_TextCopied(object sender, ICSharpCode.AvalonEdit.Editing.TextEventArgs e) |
|
{ |
|
TextEditorSideBar.Instance.PutInClipboardRing(e.Text); |
|
} |
|
|
|
public override object Control { |
|
get { return codeEditor; } |
|
} |
|
|
|
public override object InitiallyFocusedControl { |
|
get { return codeEditor.PrimaryTextEditor.TextArea; } |
|
} |
|
|
|
public override void Save(OpenedFile file, Stream stream) |
|
{ |
|
if (file != PrimaryFile) |
|
return; |
|
|
|
if (codeEditor.CanSaveWithCurrentEncoding()) { |
|
codeEditor.Save(stream); |
|
} else { |
|
int r = MessageService.ShowCustomDialog( |
|
"${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}", |
|
StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}", |
|
new StringTagPair("encoding", codeEditor.Encoding.EncodingName)), |
|
0, -1, |
|
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}", |
|
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}"); |
|
if (r == 1) { |
|
// continue saving with data loss |
|
MemoryStream ms = new MemoryStream(); |
|
codeEditor.Save(ms); |
|
ms.Position = 0; |
|
ms.WriteTo(stream); |
|
ms.Position = 0; |
|
// Read back the version we just saved to show the data loss to the user (he'll be able to press Undo). |
|
using (StreamReader reader = new StreamReader(ms, codeEditor.Encoding, false)) { |
|
codeEditor.Document.Text = reader.ReadToEnd(); |
|
} |
|
return; |
|
} else { |
|
// unfortunately we don't support cancel within IViewContent.Save, so we'll use the safe choice of UTF-8 instead |
|
codeEditor.Encoding = System.Text.Encoding.UTF8; |
|
codeEditor.Save(stream); |
|
} |
|
} |
|
} |
|
|
|
bool isLoading; |
|
|
|
public override void Load(OpenedFile file, Stream stream) |
|
{ |
|
if (file != PrimaryFile) |
|
return; |
|
isLoading = true; |
|
try { |
|
if (!file.IsUntitled) { |
|
codeEditor.PrimaryTextEditor.IsReadOnly = (File.GetAttributes(file.FileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; |
|
} |
|
|
|
codeEditor.Load(stream); |
|
// Load() causes the undo stack to think stuff changed, so re-mark the file as original if necessary |
|
if (!this.PrimaryFile.IsDirty) { |
|
codeEditor.Document.UndoStack.MarkAsOriginalFile(); |
|
} |
|
|
|
// we set the file name after loading because this will place the fold markers etc. |
|
codeEditor.FileName = file.FileName; |
|
BookmarksAttach(); |
|
} finally { |
|
isLoading = false; |
|
} |
|
} |
|
|
|
protected override void OnFileNameChanged(OpenedFile file) |
|
{ |
|
base.OnFileNameChanged(file); |
|
if (file == PrimaryFile) { |
|
FileName oldFileName = codeEditor.FileName; |
|
FileName newFileName = file.FileName; |
|
|
|
if (!string.IsNullOrEmpty(oldFileName)) |
|
SD.ParserService.ClearParseInformation(oldFileName); |
|
|
|
|
|
BookmarksNotifyNameChange(oldFileName, newFileName); |
|
// changing the filename on the codeEditor raises several events; ensure |
|
// we got our state updated first (bookmarks, persistent anchors) before other code |
|
// processes the file name change |
|
|
|
codeEditor.FileName = newFileName; |
|
|
|
SD.ParserService.ParseAsync(file.FileName, codeEditor.Document).FireAndForget(); |
|
} |
|
} |
|
|
|
public override INavigationPoint BuildNavPoint() |
|
{ |
|
int lineNumber = codeEditor.Line; |
|
string txt = codeEditor.Document.GetText(codeEditor.Document.GetLineByNumber(lineNumber)); |
|
return new TextNavigationPoint(this.PrimaryFileName, lineNumber, codeEditor.Column, txt); |
|
} |
|
|
|
void CaretChanged(object sender, EventArgs e) |
|
{ |
|
NavigationService.Log(this.BuildNavPoint()); |
|
var document = codeEditor.Document; |
|
int lineOffset = document.GetLineByNumber(codeEditor.Line).Offset; |
|
int chOffset = codeEditor.Column; |
|
int col = 1; |
|
for (int i = 1; i < chOffset; i++) { |
|
if (document.GetCharAt(lineOffset + i - 1) == '\t') |
|
col += CodeEditorOptions.Instance.IndentationSize; |
|
else |
|
col += 1; |
|
} |
|
SD.StatusBar.SetCaretPosition(col, codeEditor.Line, chOffset); |
|
} |
|
|
|
public override bool IsReadOnly { |
|
get { return codeEditor.PrimaryTextEditor.IsReadOnly; } |
|
} |
|
|
|
#region Bookmark Handling |
|
bool bookmarksAttached; |
|
|
|
void BookmarksAttach() |
|
{ |
|
if (bookmarksAttached) return; |
|
bookmarksAttached = true; |
|
foreach (SDBookmark bookmark in SD.BookmarkManager.GetBookmarks(codeEditor.FileName)) { |
|
bookmark.Document = codeEditor.Document; |
|
codeEditor.IconBarManager.Bookmarks.Add(bookmark); |
|
} |
|
SD.BookmarkManager.BookmarkAdded += BookmarkManager_Added; |
|
SD.BookmarkManager.BookmarkRemoved += BookmarkManager_Removed; |
|
|
|
PermanentAnchorService.AttachDocument(codeEditor.FileName, codeEditor.Document); |
|
} |
|
|
|
void BookmarksDetach() |
|
{ |
|
if (codeEditor.FileName != null) { |
|
PermanentAnchorService.DetachDocument(codeEditor.FileName, codeEditor.Document); |
|
} |
|
|
|
SD.BookmarkManager.BookmarkAdded -= BookmarkManager_Added; |
|
SD.BookmarkManager.BookmarkRemoved -= BookmarkManager_Removed; |
|
foreach (SDBookmark bookmark in codeEditor.IconBarManager.Bookmarks.OfType<SDBookmark>()) { |
|
if (bookmark.Document == codeEditor.Document) { |
|
bookmark.Document = null; |
|
} |
|
} |
|
codeEditor.IconBarManager.Bookmarks.Clear(); |
|
} |
|
|
|
void BookmarkManager_Removed(object sender, BookmarkEventArgs e) |
|
{ |
|
codeEditor.IconBarManager.Bookmarks.Remove(e.Bookmark); |
|
if (e.Bookmark.Document == codeEditor.Document) { |
|
e.Bookmark.Document = null; |
|
} |
|
} |
|
|
|
void BookmarkManager_Added(object sender, BookmarkEventArgs e) |
|
{ |
|
if (FileUtility.IsEqualFileName(this.PrimaryFileName, e.Bookmark.FileName)) { |
|
codeEditor.IconBarManager.Bookmarks.Add(e.Bookmark); |
|
e.Bookmark.Document = codeEditor.Document; |
|
} |
|
} |
|
|
|
void BookmarksNotifyNameChange(FileName oldFileName, FileName newFileName) |
|
{ |
|
PermanentAnchorService.RenameDocument(oldFileName, newFileName, codeEditor.Document); |
|
|
|
foreach (SDBookmark bookmark in codeEditor.IconBarManager.Bookmarks.OfType<SDBookmark>()) { |
|
bookmark.FileName = newFileName; |
|
} |
|
} |
|
#endregion |
|
|
|
public override void Dispose() |
|
{ |
|
if (trackedFeature != null) |
|
trackedFeature.EndTracking(); |
|
if (PrimaryFile != null) |
|
this.PrimaryFile.IsDirtyChanged -= PrimaryFile_IsDirtyChanged; |
|
base.Dispose(); |
|
BookmarksDetach(); |
|
codeEditor.Dispose(); |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return "[" + GetType().Name + " " + this.PrimaryFileName + "]"; |
|
} |
|
|
|
#region IMementoCapable |
|
public Properties CreateMemento() |
|
{ |
|
Properties memento = new Properties(); |
|
memento.Set("CaretOffset", codeEditor.ActiveTextEditor.CaretOffset); |
|
memento.Set("ScrollPositionY", codeEditor.ActiveTextEditor.VerticalOffset); |
|
return memento; |
|
} |
|
|
|
public void SetMemento(Properties memento) |
|
{ |
|
codeEditor.PrimaryTextEditor.ScrollToVerticalOffset(memento.Get("ScrollPositionY", 0.0)); |
|
try { |
|
codeEditor.PrimaryTextEditor.CaretOffset = memento.Get("CaretOffset", 0); |
|
} catch (ArgumentOutOfRangeException) { |
|
// ignore caret out of range - maybe file was changed externally? |
|
} |
|
} |
|
#endregion |
|
|
|
object IToolsHost.ToolsContent { |
|
get { return TextEditorSideBar.Instance; } |
|
} |
|
} |
|
}
|
|
|