/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Windows.Forms; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea.SamplePlugins.SccPlugin { /// /// The display pane for displaying ChangeSet resources. /// public class ChangeSetDisplayPane: System.Windows.Forms.UserControl, IDisplayPane { private System.Windows.Forms.RichTextBox _edtDescription; private System.Windows.Forms.Splitter splitter1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.ListBox _changedFilesList; private System.Windows.Forms.Splitter splitter2; private System.Windows.Forms.Panel panel2; private System.Windows.Forms.LinkLabel _lnkFileName; private System.Windows.Forms.RichTextBox _edtDiff; /// /// Required designer variable. /// private System.ComponentModel.Container components = null; private IResource _changeSet; private IResourceList _changeSetList; private IResourceList _selectedChangeList; private Dictionary _linkTextMap; private DateTime _colorizeStartTime; public ChangeSetDisplayPane() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // TODO: Add any initialization after the InitializeComponent call } /// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this._edtDescription = new System.Windows.Forms.RichTextBox(); this.splitter1 = new System.Windows.Forms.Splitter(); this.panel1 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); this._edtDiff = new System.Windows.Forms.RichTextBox(); this._lnkFileName = new System.Windows.Forms.LinkLabel(); this.splitter2 = new System.Windows.Forms.Splitter(); this._changedFilesList = new System.Windows.Forms.ListBox(); this.panel1.SuspendLayout(); this.panel2.SuspendLayout(); this.SuspendLayout(); // // _edtDescription // this._edtDescription.BackColor = System.Drawing.SystemColors.Control; this._edtDescription.Dock = System.Windows.Forms.DockStyle.Top; this._edtDescription.Location = new System.Drawing.Point(0, 0); this._edtDescription.Name = "_edtDescription"; this._edtDescription.ReadOnly = true; this._edtDescription.Size = new System.Drawing.Size(676, 48); this._edtDescription.TabIndex = 0; this._edtDescription.Text = ""; this._edtDescription.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(this._edtDescription_LinkClicked); // // splitter1 // this.splitter1.Dock = System.Windows.Forms.DockStyle.Top; this.splitter1.Location = new System.Drawing.Point(0, 48); this.splitter1.Name = "splitter1"; this.splitter1.Size = new System.Drawing.Size(676, 3); this.splitter1.TabIndex = 1; this.splitter1.TabStop = false; // // panel1 // this.panel1.Controls.Add(this.panel2); this.panel1.Controls.Add(this.splitter2); this.panel1.Controls.Add(this._changedFilesList); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(0, 51); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(676, 249); this.panel1.TabIndex = 2; // // panel2 // this.panel2.Controls.Add(this._edtDiff); this.panel2.Controls.Add(this._lnkFileName); this.panel2.Dock = System.Windows.Forms.DockStyle.Fill; this.panel2.Location = new System.Drawing.Point(183, 0); this.panel2.Name = "panel2"; this.panel2.Size = new System.Drawing.Size(493, 249); this.panel2.TabIndex = 2; // // _edtDiff // this._edtDiff.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this._edtDiff.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(204))); this._edtDiff.Location = new System.Drawing.Point(4, 24); this._edtDiff.Multiline = true; this._edtDiff.Name = "_edtDiff"; this._edtDiff.ReadOnly = true; this._edtDiff.Size = new System.Drawing.Size(488, 220); this._edtDiff.TabIndex = 1; this._edtDiff.Text = ""; this._edtDiff.WordWrap = false; // // _lnkFileName // this._lnkFileName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this._lnkFileName.Location = new System.Drawing.Point(4, 4); this._lnkFileName.Name = "_lnkFileName"; this._lnkFileName.Size = new System.Drawing.Size(484, 16); this._lnkFileName.TabIndex = 0; this._lnkFileName.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this._lnkFileName_LinkClicked); // // splitter2 // this.splitter2.Location = new System.Drawing.Point(180, 0); this.splitter2.Name = "splitter2"; this.splitter2.Size = new System.Drawing.Size(3, 249); this.splitter2.TabIndex = 1; this.splitter2.TabStop = false; // // _changedFilesList // this._changedFilesList.Dock = System.Windows.Forms.DockStyle.Left; this._changedFilesList.IntegralHeight = false; this._changedFilesList.Location = new System.Drawing.Point(0, 0); this._changedFilesList.Name = "_changedFilesList"; this._changedFilesList.Size = new System.Drawing.Size(180, 249); this._changedFilesList.TabIndex = 0; this._changedFilesList.SelectedIndexChanged += new System.EventHandler(this._changedFilesList_SelectedIndexChanged); // // ChangeSetDisplayPane // this.Controls.Add(this.panel1); this.Controls.Add(this.splitter1); this.Controls.Add(this._edtDescription); this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(204))); this.Name = "ChangeSetDisplayPane"; this.Size = new System.Drawing.Size(676, 300); this.panel1.ResumeLayout(false); this.panel2.ResumeLayout(false); this.ResumeLayout(false); } #endregion public Control GetControl() { return this; } public void DisplayResource( IResource resource ) { _changeSet = resource; _changeSetList = resource.ToResourceListLive(); _changeSetList.ResourceChanged += HandleChangesetChanged; _edtDescription.Text = resource.GetPropText( Core.Props.LongBody ); HighlightDescriptionLinks(); IResource repository = _changeSet.GetProp( Props.ChangeSetRepository ); RepositoryType repType = SccPlugin.GetRepositoryType( repository ); repType.OnChangesetSelected( repository, _changeSet ); _changedFilesList.BeginUpdate(); try { _changedFilesList.Items.Clear(); foreach( FileChange fileChange in resource.GetLinksOfType( FileChange.ResourceType, Props.Change ) ) { if ( Settings.HideUnchangedFiles ) { if ( !fileChange.Binary && fileChange.ChangeType == "edit" && String.IsNullOrEmpty(fileChange.Diff) ) { continue; } } _changedFilesList.Items.Add( fileChange ); } } finally { _changedFilesList.EndUpdate(); } if ( _changedFilesList.Items.Count == 0 ) { ClearSelectedChange(); _lnkFileName.Links.Clear(); } else { FileChange fileChange = (FileChange) _changedFilesList.Items [0]; _changedFilesList.SelectedItem = fileChange; if ( repType.BuildLinkToFile( repository, fileChange ) == null ) { _lnkFileName.Links.Clear(); } } } private void HighlightDescriptionLinks() { _linkTextMap = new Dictionary(); foreach( LinkRegex res in Core.ResourceStore.GetAllResources( LinkRegex.ResourceType ) ) { string regexMatch = res.RegexMatch; string regexReplace = res.RegexReplace; if ( String.IsNullOrEmpty(regexMatch) || String.IsNullOrEmpty(regexReplace)) { continue; } Regex rxMatch = new Regex( regexMatch ); foreach( Match m in rxMatch.Matches( _edtDescription.Text ) ) { _edtDescription.Select( m.Index, m.Length ); CHARFORMAT2 fmt = new CHARFORMAT2(); fmt.cbSize = Marshal.SizeOf( fmt ); fmt.dwMask = CFM.EFFECTS; fmt.dwEffects = (uint) CFM.LINK; SendMessage( _edtDescription.Handle, EditMessage.SETCHARFORMAT, SCF.SELECTION, ref fmt ); _linkTextMap [m.Value] = rxMatch.Replace( m.Value, regexReplace ); } } _edtDescription.Select( 0, 0 ); } public void HighlightWords( WordPtr[] words ) { } public void EndDisplayResource( IResource resource ) { _changeSetList.ResourceChanged -= new ResourcePropIndexEventHandler( HandleChangesetChanged ); _changeSetList.Dispose(); SetFileChangeWatch( null ); } private void SetFileChangeWatch( IResource fileChange ) { if ( _selectedChangeList != null ) { _selectedChangeList.ResourceChanged -= new ResourcePropIndexEventHandler( HandleFileChangeChanged ); _selectedChangeList.Dispose(); _selectedChangeList = null; } if ( fileChange != null ) { _selectedChangeList = fileChange.ToResourceListLive(); _selectedChangeList.ResourceChanged += new ResourcePropIndexEventHandler( HandleFileChangeChanged ); } } public void DisposePane() { Dispose(); } public string GetSelectedText( ref TextFormat format ) { return null; } public string GetSelectedPlainText() { return null; } public bool CanExecuteCommand( string command ) { return false; } public void ExecuteCommand( string command ) { } private void _changedFilesList_SelectedIndexChanged( object sender, EventArgs e ) { IResource selChange = null; FileChange fileChange = (FileChange) _changedFilesList.SelectedItem; if ( fileChange != null ) { selChange = fileChange.Resource; } SetFileChangeWatch( selChange ); UpdateSelectedChange(); } private void UpdateSelectedChange() { FileChange fileChange = (FileChange) _changedFilesList.SelectedItem; if ( fileChange != null ) { IResource repository = _changeSet.GetProp( Props.ChangeSetRepository ); RepositoryType repType = SccPlugin.GetRepositoryType( repository ); string diffText = repType.OnFileChangeSelected( repository, fileChange ); _lnkFileName.Text = repType.BuildFileName( repository, fileChange ); if ( _lnkFileName.Links.Count == 1 ) { _lnkFileName.Links [0].LinkData = fileChange; } _edtDiff.Clear(); string changeType = fileChange.ChangeType; if ( changeType == "add" ) { _edtDiff.Text = "New file"; } else if ( changeType == "delete" ) { _edtDiff.Text = "File deleted"; } else if ( fileChange.Binary ) { _edtDiff.Text = "Binary file"; } else { string diff = fileChange.Diff; if ( String.IsNullOrEmpty(diff) ) { _edtDiff.Text = diffText; } else { _edtDiff.Text = FilterWhitespaceOnlyDiffs( diff ); ColorizeDiff(); } } } else { ClearSelectedChange(); } } /// /// Processes the file diff in unified diff format. If a sequence of removed and added lines /// has differences only in whitespace, marks these lines as unchanged. /// /// The input string in unified diff format. /// The filtered string in unified diff format. private static string FilterWhitespaceOnlyDiffs( string diff ) { string[] diffLines = diff.Split( '\n' ); ArrayList resultLines = new ArrayList(); ArrayList removedLines = new ArrayList(); ArrayList addedLines = new ArrayList(); int i = 0; while( i < diffLines.Length ) { if ( !diffLines [i].StartsWith( "-" ) ) { resultLines.Add( diffLines [i++] ); continue; } while ( i < diffLines.Length && diffLines [i].StartsWith( "-" ) ) { removedLines.Add( diffLines [i++] ); } while ( i < diffLines.Length && diffLines [i].StartsWith( "+" ) ) { addedLines.Add( diffLines [i++] ); } bool whitespaceOnlyDiff = false; if ( removedLines.Count == addedLines.Count ) { whitespaceOnlyDiff = true; for( int j=0; j 1000) { return; } lineCount++; startIndex = text.IndexOf( "\n" + prefix, startIndex ); if ( startIndex < 0 ) { break; } int lineEnd = text.IndexOf( "\n", startIndex + 2 ); if ( lineEnd < 0 ) { lineEnd = text.Length; } _edtDiff.Select( startIndex+1, lineEnd-startIndex-1 ); _edtDiff.SelectionColor = color; startIndex = lineEnd; } } private void HandleChangesetChanged( object sender, ResourcePropIndexEventArgs e ) { // redisplay resource only if interesting changes occur if ( !e.ChangeSet.IsPropertyChanged( Props.Change ) && !e.ChangeSet.IsPropertyChanged( Core.PropIds.LongBody ) ) { return; } if ( !Core.UserInterfaceAP.IsOwnerThread ) { Core.UIManager.QueueUIJob(() => HandleChangesetChanged(sender, e)); return; } if ( e.Resource == _changeSet ) { EndDisplayResource( e.Resource ); if ( !e.Resource.IsDeleting ) { DisplayResource( e.Resource ); } } } private void HandleFileChangeChanged( object sender, ResourcePropIndexEventArgs e ) { if ( !Core.UserInterfaceAP.IsOwnerThread ) { Core.UIManager.QueueUIJob( () => HandleFileChangeChanged(sender, e)); return; } FileChange fileChange = (FileChange) _changedFilesList.SelectedItem; if ( fileChange != null && fileChange.Resource == e.Resource ) { UpdateSelectedChange(); } } private void ClearSelectedChange() { _edtDiff.Text = ""; _lnkFileName.Text = ""; } private void _lnkFileName_LinkClicked( object sender, LinkLabelLinkClickedEventArgs e ) { IResource repository = _changeSet.GetProp( Props.ChangeSetRepository ); RepositoryType repType = SccPlugin.GetRepositoryType( repository ); string url = repType.BuildLinkToFile( repository, (FileChange) e.Link.LinkData ); if ( url != null ) { Core.UIManager.OpenInNewBrowserWindow( url ); } } private void _edtDescription_LinkClicked( object sender, LinkClickedEventArgs e ) { if ( _linkTextMap.ContainsKey( e.LinkText ) ) { Core.UIManager.OpenInNewBrowserWindow( _linkTextMap [e.LinkText] ); } else { Core.UIManager.OpenInNewBrowserWindow( e.LinkText ); } } private enum CFM : uint { BOLD = 0x00000001, ITALIC = 0x00000002, UNDERLINE = 0x00000004, STRIKEOUT = 0x00000008, PROTECTED = 0x00000010, LINK = 0x00000020, // Exchange hyperlink extension SIZE = 0x80000000, COLOR = 0x40000000, FACE = 0x20000000, OFFSET = 0x10000000, CHARSET = 0x08000000, // CHARFORMAT effects //#define CFE_BOLD 0x0001 //#define CFE_ITALIC 0x0002 //#define CFE_UNDERLINE 0x0004 //#define CFE_STRIKEOUT 0x0008 //#define CFE_PROTECTED 0x0010 //#define CFE_LINK 0x0020 //#define CFE_AUTOCOLOR 0x40000000 // NOTE: this corresponds to // CFM_COLOR, which controls it // Masks and effects defined for CHARFORMAT2 -- an (*) indicates // that the data is stored by RichEdit 2.0/3.0, but not displayed SMALLCAPS = 0x0040, // (*) ALLCAPS = 0x0080, // Displayed by 3.0 HIDDEN = 0x0100, // Hidden by 3.0 OUTLINE = 0x0200, // (*) SHADOW = 0x0400, // (*) EMBOSS = 0x0800, // (*) IMPRINT = 0x1000, // (*) DISABLED = 0x2000, REVISED = 0x4000, // BACKCOLOR = 0x04000000, LCID = 0x02000000, UNDERLINETYPE = 0x00800000, // Many displayed by 3.0 WEIGHT = 0x00400000, SPACING = 0x00200000, // Displayed by 3.0 KERNING = 0x00100000, // (*) STYLE = 0x00080000, // (*) ANIMATION = 0x00040000, // (*) REVAUTHOR = 0x00008000, CFE_SUBSCRIPT = 0x00010000, // Superscript and subscript are CFE_SUPERSCRIPT = 0x00020000, // mutually exclusive SUBSCRIPT = CFE_SUBSCRIPT | CFE_SUPERSCRIPT, SUPERSCRIPT = SUBSCRIPT, // // CHARFORMAT "ALL" masks EFFECTS = (BOLD | ITALIC | UNDERLINE | COLOR | STRIKEOUT | /* CFE_*/ PROTECTED | LINK), ALL = (EFFECTS | SIZE | FACE | OFFSET | CHARSET), EFFECTS2 = (EFFECTS | DISABLED | SMALLCAPS | ALLCAPS | HIDDEN | OUTLINE | SHADOW | EMBOSS | IMPRINT | DISABLED | REVISED | SUBSCRIPT | SUPERSCRIPT | BACKCOLOR), ALL2 = (ALL | EFFECTS2 | BACKCOLOR | LCID | UNDERLINETYPE | WEIGHT | REVAUTHOR | SPACING | KERNING | STYLE | ANIMATION), } [StructLayout(LayoutKind.Sequential, Pack=8, CharSet=CharSet.Auto)] private struct CHARFORMAT2 { private const int LF_FACESIZE = 32; // Max size of a font name public int cbSize; public CFM dwMask; public UInt32 dwEffects; public UInt32 yHeight; public UInt32 yOffset; public int crTextColor; public Byte bCharSet; public Byte bPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)] public String szFaceName; public UInt16 wWeight; public UInt16 sSpacing; public int crBackColor; public UInt32 lcid; public UInt32 dwReserved; public UInt16 sStyle; public UInt16 wKerning; public Byte bUnderlineType; public Byte bAnimation; public Byte bRevAuthor; public Byte bReserved1; } [DllImport("user32.dll", CharSet=CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, EditMessage msg, SCF wParam, ref CHARFORMAT2 fmt); private enum EditMessage : int { FIRST = 0x400, GETLIMITTEXT = FIRST + 37, POSFROMCHAR = FIRST + 38, CHARFROMPOS = FIRST + 39, SCROLLCARET = FIRST + 49, CANPASTE = FIRST + 50, DISPLAYBAND = FIRST + 51, EXGETSEL = FIRST + 52, EXLIMITTEXT = FIRST + 53, EXLINEFROMCHAR = FIRST + 54, EXSETSEL = FIRST + 55, FINDTEXT = FIRST + 56, FORMATRANGE = FIRST + 57, GETCHARFORMAT = FIRST + 58, GETEVENTMASK = FIRST + 59, GETOLEINTERFACE = FIRST + 60, GETPARAFORMAT = FIRST + 61, GETSELTEXT = FIRST + 62, HIDESELECTION = FIRST + 63, PASTESPECIAL = FIRST + 64, REQUESTRESIZE = FIRST + 65, SELECTIONTYPE = FIRST + 66, SETBKGNDCOLOR = FIRST + 67, SETCHARFORMAT = FIRST + 68, SETEVENTMASK = FIRST + 69, SETOLECALLBACK = FIRST + 70, SETPARAFORMAT = FIRST + 71, SETTARGETDEVICE = FIRST + 72, STREAMIN = FIRST + 73, STREAMOUT = FIRST + 74, GETTEXTRANGE = FIRST + 75, FINDWORDBREAK = FIRST + 76, SETOPTIONS = FIRST + 77, GETOPTIONS = FIRST + 78, FINDTEXTEX = FIRST + 79, GETWORDBREAKPROCEX = FIRST + 80, SETWORDBREAKPROCEX = FIRST + 81, // RichEdit 2.0 messages SETUNDOLIMIT = FIRST + 82, REDO = FIRST + 84, CANREDO = FIRST + 85, GETUNDONAME = FIRST + 86, GETREDONAME = FIRST + 87, STOPGROUPTYPING = FIRST + 88, SETTEXTMODE = FIRST + 89, GETTEXTMODE = FIRST + 90, SETTEXTEX = FIRST + 97, } private enum SCF : int { SELECTION = 0x0001, WORD = 0x0002, DEFAULT = 0x0000, // Set default charformat or paraformat ALL = 0x0004, // Not valid with SCF_SELECTION or SCF_WORD USEUIRULES = 0x0008, // Modifier for SCF_SELECTION; says that // format came from a toolbar, etc., and // hence UI formatting rules should be // used instead of literal formatting ASSOCIATEFONT = 0x0010, // Associate fontname with bCharSet (one // possible for each of Western, ME, FE, // Thai) NOKBUPDATE = 0x0020, // Do not update KB layput for this change // even if autokeyboard is on ASSOCIATEFONT2 = 0x0040, // Associate plane-2 (surrogate) font } } internal partial class FileChange { public override string ToString() { return String.Format("{0}#{1} ({2})", Name, Revision, ChangeType); } } }