Document objects are used to represent an open document in the IDE. To contrast this abstraction with that provided by the Window object: A Window object is used to represent the physical UI aspects of a document window, whereas a Document object is used to represent the physical document that is being displayed within that document window. A document could be a designer, such as the Windows Forms designer, or it could be a text-based document such as a readme file or a C# code file open in an editor. Just as you get a list of all open windows using the DTE.Windows collection, you can use the DTE.Documents collection to retrieve a list of all open documents: Dim documents As Documents = DTE.Documents The Documents collection is indexed by the document's Name property, which is, in effect, the document's filename without the path information. This makes it easy to quickly retrieve a Document instance: Dim documents As Documents = DTE.Documents Dim readme As Document = documents.Item("ReadMe.txt") Using the Document object, you can Close the document (and optionally save changes) Retrieve the filename and path of the document Determine whether the document has been modified since the last time it was saved Determine what, if anything, is currently selected within the document Obtain a ProjectItem instance representing the project item that is associated with the document Read and edit the contents of text documents Table 10.11 contains the member descriptions for the Document object. Table 10.11. Document MembersProperty | Description |
---|
ActiveWindow | The currently active window associated with the document (null or Nothing value indicates that there is no active window). | Collection | The collection of Document objects to which this instance belongs. | DTE | The root-level DTE object. | Extender | Returns a Document extender object. | ExtenderCATID | The extender category ID for the object. | ExtenderNames | A list of extenders available for the current Document object. | FullName | The full path and filename of the document. | Kind | A GUID representing the kind of document. | Name | The name (essentially, the filename without path information) for the document. | Path | The path of the document's file excluding the filename. | ProjectItem | The ProjectItem instance associated with the document. | Saved | Indicates whether the solution has been saved since the last modification. | Selection | An object representing the current selection in the document (if any). | Windows | The Windows collection containing the window displaying the document. |
Method | Description |
---|
Activate | Moves the focus to the document. | Close | Closes the document. You can indicate, with a vsSaveChanges enum value, whether the window's hosted document should be saved or not saved, or whether the IDE should prompt the user to make that decision. | NewWindow | Opens the document in a new window and returns the new window's Window object. | Object | Returns an object proxy that represents the window and can be referenced by name. | Redo | Re-executes the last user action in the document. | Save | Saves the document. | Undo | Reverses the last used action in the document. |
Text Documents As we mentioned, documents can have textual or nontextual content. For those documents with textual content, a separate objectTextdocumentexists. The TexTDocument object provides access to control functions specifically related to text content. If you have a valid Document object to start with, and if that document object refers to a text document, then a Textdocument instance can be referenced from the Document.Object property like this: Dim doc As TextDocument doc = myDocument.Object Table 10.12 contains the Textdocument members. Table 10.12. Textdocument MembersProperty | Description |
---|
DTE | The root-level DTE object | EndPoint | A TextPoint object positioned at the end of the document | Parent | Gets the parent object of the text document | Selection | Returns a TextSelection object representing the currently selected text in the document | StartPoint | A TextPoint object positioned at the start of the document |
Method | Description |
---|
ClearBookmarks | Removes any unnamed bookmarks present in the document | CreateEditPoint | Returns an edit point at the specific location (if no location is specified, the beginning of the document is assumed) | MarkText | Bookmarks lines in the document that match the specified string pattern | ReplacePattern | Replaces any text in the document that matches the pattern |
Tip A text document will be represented by both a Document instance and a Textdocument instance. Nontext documents, such as a Windows form open in a Windows Forms designer window, will have a Document representation but no corresponding Textdocument representation. Unfortunately, there isn't a great way to distinguish whether a document is text based or not during runtime. One approach is to attempt a cast or assignment to a Textdocument object and catch any exceptions that might occur during the assignment. Two Textdocument methods are useful for manipulating bookmarks within the document: ClearBookmarks will remove any unnamed bookmarks from the document, and MarkText will perform a string pattern search and place bookmarks against the resulting document lines. A simple macro to bookmark For loops in a VB document is presented in Listing 10.10. Listing 10.10. Bookmarking For Loops in a VB Document Imports EnvDTE Imports EnvDTE80 Imports Microsoft.VisualStudio.CommandBars Imports System.Diagnostics Imports System.Windows.Forms Public Module MacroExamples ' Bookmark all 'For' tokens in the current ' document Public Sub BookmarkFor() Dim doc As Document Dim txtDoc As TextDocument ' Reference the current document doc = DTE.ActiveDocument ' Retrieve a TextDocument instance from ' the document txtDoc = doc.Object ' Call the MarkText method with the 'For' string Dim found As Boolean = _ txtDoc.MarkText("For", vsFindOptions.vsFindOptionsFromStart) ' MarkText returns a boolean flag indicating whether or not ' the search pattern was found in the textdocument If found Then MessageBox.Show("All instances of 'For' have been bookmarked.") Else MessageBox.Show("No instances of 'For' were found.") End If End Sub End Module | The other key functionality exposed by the Textdocument object is the capability to read and edit the text within the document. Editing Text Documents From a Visual Studio perspective, text in a text document actually has two distinct "representations": a virtual one and a physical one. The physical representation is the straight, unadulterated code file that sits on disk. The virtual representation is what Visual Studio presents on the screen: It is an interpreted view of the text in the code file that takes into account various editor document features such as code outlining/regions, virtual spacing, word wrapping, and so on. Figure 10.7 shows this relationship. When displaying a text document, Visual Studio reads the source file into a text buffer, and then the text editor presents one view of that text file to you (based on options you have configured for the editor). Figure 10.7. Presentation of text documents within the IDE. Text in a document is manipulated or read either on the buffered text or on the "view" text that you see in the editor. There are four different automation objects that allow you to affect text; two work on the text buffer and two work on the editor view. For the text buffer: TextPoint objects are used to locate specific points within a text document. By querying the TextPoint properties, you can determine the line number of the text point, the number of characters it is offset from the start of a line, the number of characters it is offset from the start of the document, and its display column within the text editor window. You can also retrieve a reference to a CodeModel object representing the code at the text point's current location. The EditPoint object inherits from the TextPoint object; this is the primary object used for manipulating text in the text buffer. You can add, delete, or move text using edit points, and they can be moved around within the text buffer. And, for the editor view: The VirtualPoint object is equivalent to the TextPoint object except that it can be used to query text locations that reside in the "virtual" space of the text view (virtual space is the whitespace that exists after the last character in a document line). VirtualPoint instances are returned through the TextSelection object. The TextSelection object operates on text within the text editor view as opposed to the text buffer and is equivalent to the EditPoint interface. When you use the TextSelection object, you are actively affecting the text that is being displayed within the text editor. The methods and properties of this object, therefore, end up being programmatic approximations of the various ways that you would manually affect text: You can page up or page down within the view; cut, copy, and paste text; select a range of text; or even outline and expand or collapse regions of text. Because the VirtualPoint object is nearly identical to the TextPoint object, and the TextSelection object is nearly identical to the EditPoint object, we won't bother to cover each of these four objects in detail. Instead, we will focus on text buffer operations using EditPoint and TextPoint. You should be able to easily apply the concepts here to the text view. Because EditPoint objects expose the most functionality and play the central role with text editing, we have provided a list of their type members in Table 10.13. Table 10.13. EditPoint2 MembersProperty | Description |
---|
AbsoluteCharOffset | The number of characters from the start of the document to the current location of the edit point | AtEndOfDocument | Boolean flag indicating whether the point is at the end of the document | AtEndOfLine | Boolean flag indicating whether the point is at the end of a line in the document | AtStartOfDocument | Boolean flag indicating whether the point is at the beginning of the document | AtStartOfLine | Boolean flag indicating whether the point is at the start of a line in the document | CodeElement | Returns the code element that maps to the edit point's current location | DisplayColumn | The column number of the edit point | DTE | Returns the root automation DTE object | Line | The line number where the point is positioned | LineCharOffset | The character offset, within a line, of the edit point | LineLength | The length of the line where the edit point is positioned | Parent | Returns the parent object of the EditPoint2 object |
Method | Description |
---|
ChangeCase | Changes the case of a range of text | CharLeft | Moves the edit point to the left the specified number of characters | CharRight | Moves the edit point to the right the specified number of characters | ClearBookmark | Clears any unnamed bookmarks that exist on the point's current line location | Copy | Copies a range of text to the Clipboard | CreateEditPoint | Creates a new EditPoint2 object at the same location as the current EditPoint2 object | Cut | Cuts a range of text and places it on the Clipboard | Delete | Deletes a range of text from the document | DeleteWhitespace | Deletes any whitespace found around the edit point | EndOfDocument | Moves the edit point to the end of the document | EndOfLine | Moves the edit point to the end of the current line | EqualTo | A Boolean value indicating whether the edit point's AbsoluteCharOffset value is equal to another edit point's offset | FindPattern | Finds any matching string patterns in the document | GetLines | A string representing the text between two lines in the document | GetText | A string representing the text between the edit point and another location in the document | GreaterThan | A Boolean value indicating whether the edit point's AbsoluteCharOffset value is greater than another edit point's offset | Indent | Indents the selected lines by the given number of levels | Insert | Inserts a string into the document, starting at the edit point's current location | InsertFromFile | Inserts the entire contents of a text file into the document starting at the edit point's current location | LessThan | Returns a Boolean value indicating whether the edit point's AbsoluteCharOffset value is less than another edit point's offset | LineDown | Moves the point down one or more lines | LineUp | Moves the point up one or more lines | MoveToAbsoluteOffset | Moves the edit point to the given character offset | MoveToLineAndOffset | Moves the edit point to the given line and to the character offset within that line | MoveToPoint | Moves the edit point to the location of another EditPoint or TextPoint object | NextBookmark | Moves the edit point to the next available bookmark in the document | OutlineSection | Creates an outline section between the point's current location and another location in the document | PadToColumn | Pads spaces in the current line up to the indicated column number | Paste | Pastes the contents of the Clipboard to the edit point's current location | PreviousBookmark | Moves the edit point to the previous bookmark | ReadOnly | Returns a Boolean flag indicating whether a text range in the document is read only | ReplacePattern | Replaces any text that matches the provided pattern | ReplaceText | Replaces a range of text with the provided string | SetBookmark | Creates an unnamed bookmark on the edit point's current line in the document | StartOfDocument | Moves the edit point to the start of the document | StartOfLine | Moves the edit point to the beginning of the line where it is positioned | TRyToShow | Attempts to display the point's current location within the text editor window | Unindent | Removes the given number of indentation levels from a range of lines in the document | WordLeft | Moves the edit point to the left the given number of words | WordRight | Moves the edit point to the right the given number of words |
Now let's look at various text manipulation scenarios. Adding Text EditPoint objects are the key to adding text, and you create them either by using a Textdocument object or by using a TextPoint object. A TextPoint instance can create an EditPoint instance in its same exact location by calling TextPoint.CreateInstance. With the Textdocument type, you can call the CreateEditPoint method and pass in a valid TextPoint. Because TextPoint objects are used to locate specific points in a document, a TextPoint object is leveraged as an input parameter to CreateEditPoint. In essence, the TextPoint object tells the method where to create the edit point. If you do not provide a TextPoint object, the edit point will be created at the start of the document. This code snippet shows an edit point being created at the end of a document: Dim doc As Document = DTE.ActiveDocument Dim txtDoc As TextDocument = doc.Object Dim tp As TextPoint = txtDoc.EndPoint Dim ep As EditPoint2 = txtDoc.CreateEditPoint(tp) ' This line of code would have the same effect ep = tp.CreateEditPoint After creating an edit point, you can use it to add text into the document (remember, you are editing the buffered text whenever you use an EditPoint object). To inject a string into the document, you use the Insert method: ' Insert a C# comment line ep.Insert("// some comment") You can even grab the contents of a file and throw that into the document with the EditPoint.InsertFromFile method: ' Insert comments from a comments file ep.InsertFromFile("C:\Contoso\std comments.txt") Editing Text The EditPoint object supports deleting, replacing, cutting, copying, and pasting text in a document. Some of these operations require more than a single point to operate. For instance, if you wanted to cut a word or an entire line of code from a document, you would need to specify a start point and end point that define that range of text (see Figure 10.8). Figure 10.8. Using points within a document to select text. This snippet uses two end points, one at the start of a document and one at the end, to delete the entire contents of the document: Dim doc As Document = DTE.ActiveDocument Dim txtDoc As TextDocument = doc.Object Dim tpStart As TextPoint = txtDoc.StartPoint Dim tpEnd As TextPoint = txtDoc.EndPoint Dim epStart As EditPoint2 = txtDoc.CreateEditPoint(tpStart) Dim epEnd As EditPoint2 = txtDoc.CreateEditPoint(tpEnd) epStart.Delete(epEnd) Besides accepting a second EditPoint, the methods that operate on a range of text will also accept an integer identifying a count of characters. This also has the effect of defining a select. For example, this snippet cuts the first 10 characters from a document: epStart.Cut(10) Repositioning an EditPoint After establishing an EditPoint, you can move it to any given location in the document by using various methods. The CharLeft and CharRight methods will move the point any number of characters to the left or right, while the WordLeft and WordRight methods perform the same operation with words: ' Move the edit point 4 words to the right epStart.WordRight(4) The LineUp and LineDown methods will jog the point up or down the specified number of lines. You can also move EditPoints to any given line within a document by using MoveToLineAndOffset. This method will also position the point any number of characters into the line: ' Move the edit point to line 100, and then ' in 5 characters to the right epStart.MoveToLineAndOffset(100, 5) The macro code in Listing 10.11 pulls together some of the areas that we have covered with editing text documents. This macro and its supporting functions illustrate the use of EditPoints to write text into a document. In this case, the macro automatically inserts a comment "flowerbox" immediately preceding a routine. To accomplish this, the macro goes through the following process: A reference is obtained for the current document in the IDE. The active cursor location in that document is obtained via the Textdocument.Selection.ActivePoint property. An EditPoint is created using the VirtualPoint returned from the ActivePoint. A second EditPoint is then created; these two points are used to obtain the entire content of the routine definition line. The routine definition is then parsed to try to ferret out items such as its name, return value, and parameter list. A string is built using the routine information and is inserted into the text document using an EditPoint. Listing 10.11. Inserting Comments into a Text Window Imports EnvDTE Imports EnvDTE80 Imports Microsoft.VisualStudio.CommandBars Imports System Imports System.Collections Imports System.Diagnostics Imports System.Text Imports System.Windows.Forms Public Module MacroExamples ' This routine demonstrates various text editing scenarios ' using the EditPoint and TextPoint types. If you place your ' cursor on a subroutine or function, it will build a default ' "flower box" comment area, insert it immediately above the ' sub/function, and outline it. ' ' To use: ' 1) put cursor anywhere on the Sub/Function line ' 2) run macro ' The macro will fail silently (e.g., will not insert any ' comments) if it is unable to determine the start ' of the Sub/Function ' Public Sub InsertTemplateFlowerbox() ' Get reference to the active document Dim doc As Document = DTE.ActiveDocument Dim txtDoc As TextDocument = doc.Object Dim isFunc As Boolean Try Dim ep As EditPoint2 = txtDoc.Selection.ActivePoint.CreateEditPoint() ep.StartOfLine() Dim ep2 As EditPoint2 = ep.CreateEditPoint() ep2.EndOfLine() Dim lineText As String = ep.GetText(ep2).Trim() If InStr(lineText, " Function ") > 0 Then isFunc = True ElseIf InStr(lineText, " Sub ") > 0 Then isFunc = False Else Exit Sub End If ' Parse out info that we can derive from the routine ' definition: the return value type (if this is a function), ' the names of the parameters, and the name of the routine. Dim returnType As String = "" If isFunc Then returnType = ParseRetValueType(lineText) End If Dim parameters As String() = ParseParameters(lineText) Dim name As String = ParseRoutineName(lineText) Dim commentBlock As String = BuildCommentBlock(isFunc, name, _ returnType, parameters) ' Move the edit point up one line (to position ' immediately preceding the routine) ep.LineUp(1) ' Give us some room by inserting a new blank line ep.InsertNewLine() ' Insert our comment block ep.Insert(commentBlock.ToString()) Catch ex As Exception End Try End Sub Private Function BuildCommentBlock(ByVal isFunc As Boolean, _ ByVal name As String, _ ByVal returnType As String, ByVal parameters As String()) Try Dim comment As StringBuilder = New StringBuilder() ' Build up a sample comment block using the passed in info comment.Append("''''''''''''''''''''''''''''''''''''''''''''''''") comment.Append(vbCrLf) comment.Append("' Routine: " + name) comment.Append(vbCrLf) comment.Append("' Description: [insert routine desc here]") comment.Append(vbCrLf) comment.Append("'") comment.Append(vbCrLf) If isFunc Then comment.Append("' Returns: A " & returnType & _ " [insert return value description here]") End If comment.Append(vbCrLf) comment.Append("'") comment.Append(vbCrLf) comment.Append("' Parameters:") comment.Append(vbCrLf) For i As Integer = 0 To parameters.GetUpperBound(0) comment.Append("' ") comment.Append(parameters(i)) comment.Append(": [insert parameter description here]") comment.Append(vbCrLf) Next comment.Append("''''''''''''''''''''''''''''''''''''''''''''''''") Return comment.ToString() Catch ex As Exception Return "" End Try End Function Private Function ParseRetValueType(ByVal code As String) As String Try ' Parse out the return value of a function (VB) ' Search for 'As', starting from the end of the string Dim length As Integer = code.Length Dim index As Integer = code.LastIndexOf(" As ") Dim retVal As String = code.Substring(index + 3, length - (index + 3)) Return retVal.Trim() Catch ex As Exception Return "" End Try End Function Private Function ParseParameters(ByVal code As String) As String() Try ' Parse out the parameters specified (if any) for ' a VB sub/func definition Dim length As Integer = code.Length Dim indexStart As Integer = code.IndexOf("(") Dim indexEnd As Integer = code.LastIndexOf(")") Dim params As String = code.Substring(indexStart + 1, _ indexEnd - (indexStart + 1)) Return params.Split(",") Catch ex As Exception Return Nothing End Try End Function Private Function ParseRoutineName(ByVal code As String) As String Try Dim name As String Dim length As Integer = code.Length Dim indexStart As Integer = code.IndexOf(" Sub ") Dim indexEnd As Integer = code.IndexOf("(") If indexStart = -1 Then indexStart = code.IndexOf(" Function ") If indexStart <> -1 Then indexStart = indexStart + 9 End If Else indexStart = indexStart + 5 End If name = code.Substring(indexStart, indexEnd - indexStart) Return name.Trim() Catch ex As Exception Return "" End Try End Function End Module | |