Good thing to know when making a custom editor

Good thing to know when making a custom editor
By Matthew Manela (Developer Solution Intern)

Here’s a walkthrough to a problem I ran into when I was trying to create a custom editor that lets you change what language the code window is in on the fly. 

Here is the situation: You are developing a VSIP package for Visual Studio.  In this package you want to create a custom editor that will open a simple xml file called simple.myxml which contains data like this:

<Data>

  <Title>My Loaded File</Title>

  <Language>C#</Language>

  <Code>         

      string word = "editor";

      foreach(char letter in word.ToCharArray())

      {

            Console.WriteLine(letter);

      }         

  </Code>

</Data>

 

Your editor will read this xml file and parse it and populate two fields in an editor control.  One is the title field which is just a text box that will contain the text between the Title tags.  The other is a control which contains an IVsCodeWindow with an IVsTextBuffer attached to it.  This text buffer is completely in memory and has no association with a file on disk.  The code windows language service will be set based upon the text in the Language tags and the text in the code window will be loaded with the text between the Code tags.  The finished result after loaded simple.sml is something that looks like this:

First Code Editor displaying correct color coding

This all seems to have worked well. The file loaded was parsed correctly and was loaded into my editor. The color coding worked so we have nice C# color coding. At this point you think “wow, so easy”. Then you load your second file that has different code and you see this:

Second Code Editor not displaying correct color coding

It seems now only the text color coded correctly and nothing else did.  But what went wrong.  If you try this with a J# language service you see the same problem, but you don’t see it with the VB language service.

The reason this isn’t working has to do with how the C# and J# language services figure out if two text buffers are the same.  They check something called the BufferMoniker.  Usually if your text buffer is directly correlated with a file on disk the buffer moniker will be the filename.  However, since our text buffer is completely in memory our buffer moniker is an empty string.  This caused a problem only when you open your second file.  The first file color codes fine but when you open the second the language service will make a check and say “Is the current buffer moniker the same as the previous one then don’t refresh the color coding lexical data”. In this case it will check is empty string equal to empty string, then don’t refresh lexical data.

This problem is hard to figure out but the solution isn’t hard.  You need to set a unique buffer moniker for each of your text buffers.  You can set the buffer moniker uniquely using a generated Guid and your IVsTextBuffer object.

    IVsTextBuffer myTextBuffer;//defined elsewhere

   

 

    IVsUserData userData = (IVsUserData) myTextBuffer;

    string uniqueMoniker = Guid.NewGuid().ToString();

    Guid bufferMonikerGuid = typeof(IVsUserData).GUID;

    userData.SetData(ref bufferMonikerGuid, uniqueMoniker);

That is all you need.  Before your load your doc data just set the buffer moniker for the text buffer you created and now color coding will work for more than one window. 

Not so Fast … We are not done yet!

Depending on how you are inserting text into your code window you can run into more problems. Let’s say you use the following method to insert text into the code window:

      void SetText(string text)

    {

 

        IVsTextLines vsTextLines;

        vsCodeWindow.GetBuffer(out vsTextLines);

        Object oObj;

        vsTextLines.CreateEditPoint(0, 0, out oObj);

        EditPoint editPoint = (EditPoint)oObj;

        editPoint.Insert(text);

 

    }

This worked fine until you added the buffer moniker. The problem here is that the automation model (which contains EditPoint’s) makes an assumption. If a buffer moniker is not empty that means there is a file on disk. Since your buffer moniker isn’t a file on disk then CreateEditPoint will fail and oObj will be null. So the wonderfully simple solution above has broken your code.

The answer to this problem is: don’t use the automation model. Ideally you would be able to use the automation model to edit the text buffer but given that limitation you can still get SetText to work. You can use ReplaceLines in IVsTextLines to replace the content in the text buffer with other text.

public void SetText(string text)

    {

        IVsTextLines vsTextLines;

        vsCodeWindow.GetBuffer(out vsTextLines);

        int endLine, endCol;

        textLines.GetLastLineIndex(out endLine, out endCol);

        int len = (text == null) ? 0 : newText.Length;

        //fix location of the string

        IntPtr pText = Marshal.StringToCoTaskMemAuto(text);

        try

        {

            textLines.ReplaceLines(0, 0, endLine, endCol, pText, len, null);

        }

        finally

        {

            Marshal.FreeCoTaskMem(pText);

        }

    }

Now your custom editor control works. Often a problem that seems impossible has a simple solution that’s really hard to find.

Using the ClassName() snippet function – an operator declaration snippet example

A previous tip of the week mentioned how to use the SimpleTypeName() snippet function.  Here’s how to use the SimpleTypeName() function alongside the ClassName() function.  The ClassName() function will automatically substitute the name of the outer class. 

For example, if you had

class Class1

{

  

}

You could make a snippet that used the ClassName function to automatically generate

public static Class1 operator +(Class1 one, Class1 two)

{

   throw new System.NotImplementedException();

}

Here’s the snippet to make the operator declaration used above:

<?xml version="1.0" encoding="utf-8"?>

<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

  <CodeSnippet Format="1.0.0">

    <Header>

      <Title>Addition operator</Title>

      <Shortcut>addop</Shortcut>

      <Description>Code snippet to create an addition operator for a class</Description>

      <Author>Microsoft Corporation</Author>

      <SnippetTypes>

        <SnippetType>Expansion</SnippetType>

      </SnippetTypes>

    </Header>

    <Snippet>

      <Declarations>

        <Literal Editable="false">

          <ID>EnclosingClass</ID>

            <Function>ClassName()</Function>

        </Literal>

        <Literal Editable="false">

          <ID>NotImplementedException</ID>

          <Function>SimpleTypeName(global::System.NotImplementedException)</Function>

        </Literal>                                             

      </Declarations>

      <Code Language="csharp"><![CDATA[

    public static $EnclosingClass$ operator +($EnclosingClass$ first, $EnclosingClass$ second)

    {

        throw new $NotImplementedException$("Method not yet implemented");

    }]]>

      </Code>

    </Snippet>

  </CodeSnippet>

</CodeSnippets>

Tags: VSTips
Suggest a Tip!

Getting more space between your variables and their declarations in C#

Let’s say you want to keep your formatting rules enabled, but want to get some additional space between your variables and their declarations. You can put a C-style inline comment in the whitespace between the two. For example,

class Class1

{

   int/**/ a = 1;

   int/**/ b = 2;

}

 

Note that you could do something like this, by adding the spaces after hitting the semicolon at the end.

int

a = 1;

 

However, if you ever format the document, format that selection, do something on that line to invoke formatting (like re-entering the semicolon), the line will go back to

int

a = 1;

 

Tags: VSTips
Suggest a Tip!

How to change the generated method stub code for C#

Before you apply this tip…

  • I strongly recommend that you backup any snippets installed by VS that you may want to modify.  Take it from a former code Snippet QA that you will want to do this.
  • This tip requires having access to your Program Files directory, so use appropriate caution
  • Do not change the title for any of the VS installed snippets

Create a function that hasn’t been defined yet and use the smart tag to generate a method stub.

Function not defined yet

You’ll get a method stub defined for bar() that throws a new Exception class.

Default Generated Method Stub

But let’s say instead of throwing an Exception class, you wanted it to throw NotImplementedException.

Open up
\Program Files\Microsoft Visual Studio 8\VC#\Snippets\1033\Refactoring\MethodStub.snippet

And change
<Function>SimpleTypeName(global::System.Exception)</Function>
to
<Function>SimpleTypeName(global::System.NotImplementedException)</Function>

You could use this approach to define your custom method stubs as you see fit.

Tags: VSTips
Suggest a Tip!

Customize your Find in Files Results experience!

Another awesome hidden editor feature…

You can customize your Find in Files results to show what you want to see and how you want to see it.

Example:  You don’t want to view the entire file path shown in the Find Results tool window.

Find all "using", Match case, Whole word, Subfolders, Find Results 1, "Entire Solution"

C:\Documents and Settings\someuser\Local Settings\Temp\~vs90.cs(1):using System;

Matching lines: 1 Matching files: 1 Total files searched: 1

Instructions:  (since these involve modifying registry settings, please use at your own risk!)

1. Go to HKCU\Software\Microsoft\VisualStudio\8.0\Find
2. Add a new string called Find result format with a value of $f$e($l,$c):$t\r\n where

$f is the filename

$e is the extension

$l is the line

$c is the column

$t is the text on the line

Note:  You don’t have to restart Visual Studio to pick up on your registry changes.

Results:

Find all "using", Match case, Whole word, Subfolders, Find Results 1, "Entire Solution"

~vs90.cs(1,1):using System;

Matching lines: 1 Matching files: 1 Total files searched: 1

Full list of items you can specify in the registry

Files

$p      path                       

$f      filename               

$v      drive/unc share            

$d      dir                        

$n      name                       

$e      .ext                       

Location

$l      line                       

$c      col                        

$x      end col if on first line, else end of first line

$L      span end line

$C      span end col

Text

$0      matched text               

$t      text of first line

$s      summary of hit

$T      text of spanned lines

Char

\n      newline                   

\s      space                     

\t      tab                       

\\      slash                    

\$      $                         

If you come up with a great combination of values, please leave a comment and share with the group!

Tags: VSTips

View original tips

Emacs and Brief Emulations Updates Available

Dylan, one of our editor devs, has posted some workarounds to Emacs and Brief Emulations bugs on GotDotNet.

The Brief Emulation Add-in addresses the following issues:

  • Bug FDBK38309:  BRIEF editor emulation for cut with no selection incorrect 
  • Bug FDBK40426:  Paste of code block incorrectly indents first line 
  • Bug FDBK40415:  Brief mode inline paste changes to line paste

The Emacs Emulation Macro addresses the following issues:

  • Bug FDBK41933: Emacs Keybinding difficulties
  • PageDown or PageUp resets selection start

Note that these projects are in beta, and a new release should be out soon. Please join the project to receive news and download announcements (via the Alerts Console, once you have joined the project).

Happy Visual Studio’ing!

Tags: VSTips
Suggest a Tip!

The top 10 most visited tips

Today’s post marks the 100th Tip on the Tip of the Week!  To celebrate the occasion, let’s recap our top 10 most visited* tips.

#10 – How to bind Keyboard Shortcuts to commands

#9 – How to do column selection

#8 – What C# books to read

#7 – How to optimize Visual Studio for Multi-Monitor setups

#6 – How to save your favorite window layouts in VS .NET 2003

#5 – Type-ahead selection works in Solution Explorer

#4 – Use Ctrl+Alt+DownArrow to quickly access all your open files

#3 – Use Ctrl-K Ctrl-C to comment selection and Ctrl-K Ctrl-U to uncomment selection

#2 – How to use Full Screen Mode

And the most visited Tip on Tip of the Week…

#1 – How to have fun with the Find Combo box

Prior to starting the Tip of the Week, I wrote this entry that would have blown all of these out of the water.

Guidelines – a hidden feature for the Visual Studio Editor

*most visited = number of web hits (how many people visited the website through a web browser) + RSS hits (how many people read the entry via a RSS reader)

Disable Add-ins on Start-Up by holding the Left-Shift key down

If you ever want to disable add-ins from starting on launch, you can hold down the Left-Shift key while VS is loading.  If you find yourself running into random crashes, you might want to try disabling add-ins (either by this Left-Shift method or going to the Tools – Add-in Manager) to determine if it is a VS issue or an Add-in issue.  You’ll still be able to manually restart your Add-in during this VS session.

If you are seeing random VS crashes and you have Add-ins installed, the Visual Studio Team on the MSDN Forums would love to know if disabling add-ins had any effect on the crashes.

For any questions related to Add-ins, please check out the Extensibility Forums!

Got an Add-in Tip?  Submit it here!

Happy Visual Studio’ing!

VS Macro to Build a Web.SiteMap file from your project system file layout

Today’s tip comes from Scott Allen’s blog.  Scott has provided a VS 2005 macro that will dynamically build a web.sitemap file based on the files in your project.

For any questions related to ASP.NET, please check out the ASP.NET forums!

Got an ASP.NET Tip?  Submit it here!

Happy Visual Studio’ing!

How a project max path and solution name length is calculated

You’ve may have noticed that sometimes your project name is too long where other times it is accepted in the New Project Dialog.  As explained by Paul on the Visual Studio General Forums

The OS limit is 260 characters (MAX_PATH).  However, the computation is:

  Path length + 1 (separator) +
   Solution name length + 1 (separator) +
   Project name length + 1 (separator) +
   Project name length +
   80 (Reserved space)

The project name length is added twice: Once for the folder name and once for the file that holds your project itself (.vbproj, .csproj etc.). The 80 additional reserved characters are for the files that you place in the project. Most new project templates create several sub-folders for intermediate files that help with like intellisense and debugging. While we may not use the full 80 characters, it is a sensible limit.

Using Paul’s calculation above, let’s create a Max-Length project / solution named
ThisSolutionIsThirtyFiveCharacters  (35 characters long)
at location
C:\Documents and Settings\saraf\My Documents\Visual Studio 2005\Projects  (73 characters long)

Doing the math, we get
73 + 1 +
35 + 1 +
35 + 1 +
35 +
80
= 261

So, VS will accept creating a solution / project ThisSolutionIsThirtyFiveCharacters at C:\Documents and Settings\saraf\My Documents\Visual Studio 2005\Projects.