Given a SyntaxNode instance, how can I open appropriate source code file and place cursor at position where the node resides?
I'm writing some simple analyzer. I'm able start it on demand and get a syntax node from current cursor position. But I can't figure out how do I get back to editor from result syntax node. I see the node has a Span property but other than that I don't see any info. The Node I want to show can be in some other file that may not be even opened.
I would like to have behavior similar to "go to..." command for the result of my search.
I spend whole day on this but finally got it.
private void selectNodeInEditor(SyntaxNode n) {
var cm = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel));
var tm = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
var ws = (Workspace)cm.GetService<VisualStudioWorkspace>();
var did = ws.CurrentSolution.GetDocumentId(n.SyntaxTree);
ws.OpenDocument(did);
tm.GetActiveView(1, null, out var av);
var sp = n.GetLocation().GetMappedLineSpan().StartLinePosition;
var ep = n.GetLocation().GetMappedLineSpan().EndLinePosition;
av.SetSelection(sp.Line, sp.Character, ep.Line, ep.Character);
}
Syntax nodes have a GetLocation() method, which returns a Microsoft.CodeAnalysis.Location for that syntax node, so that's one way to get a location object from the SyntaxNode.
var nodeLocation = syntaxNode.GetLocation();
You can also get location information from a symbol using the Locations property on the ISymbol interface, if you happen to need the symbol as well. From the docs:
Gets the locations where the symbol was originally defined, either in source or metadata. Some symbols (for example, partial classes) may be defined in more than one location.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.isymbol?view=roslyn-dotnet
This may be prefereable, as depending on the type of the SyntaxNode, you may want to get the symbol's original definition rather than the symbol itself e.g. you could get a class declaration from a field's type. This can be done with the OriginalDefinition property.
// assumes there's a SemanticModel in scope and your node is called synatxNode
var syntaxNodeSymbol = semanticModel.GetSymbolInfo(syntaxNode).Symbol;
// make sure syntaxNodeSymbol is null checked
var nodeLocation = syntaxNodeSymbol.Locations[0];
var originalNodeLocation = syntaxNodeSymbol.OriginalDefinition.Locations[0];
As for actually jumping to the node, this question would probably be a good place to start:
Go to definition from text position with roslyn
However, this is already possible in Visual Studio by double clicking on the message in the error list panel. Furthermore, any code fixes you want to make at that location will show up in the code fix window.
Related
Original Question
I have this expandable folder UIObject that I need to be able to expand to show all the subfolders. It can not be double clicked as this does not expand the folders. I saw from this documentation, https://admhelp.microfocus.com/leanft/en/14.02/NetSDKReference/HP.LFT.SDK~HP.LFT.SDK.Java.ITreeView.html as well as some others that there is a concept of an ITreeView and an ITreeViewNode.
How can I expand this element? I really just need some examples in code of how we can take an object, defeine it as a ITreeView and ITreeView node and expand it.
Result
Even though it is not the best solution, it is possible to do this using the Workaround suggested below and this is the method that made it happen
public void ExpandFolder(int index)
{
IUiObject folder = ViewPage.FolderExplorer.Describe<IUiObject>(new UiObjectDescription
{
ObjectName = "TreeViewItem",
Index = (uint)index
});
var expandButton = folder.AbsoluteLocation;
expandButton.X = expandButton.X + 2;
expandButton.Y = expandButton.Y + 4;
Mouse.Click(expandButton);
GeneralUtilities.Sleep(1);
}
In this case, there was a small drop down arrow to the left of the element. I couldn't identify that, so I identified the folder and manipulated the click. If anyone stumbles upon this and knows a more direct way to do this using LeanFT I would very much like to see an example. If not, and you are here trying to find help - I hope this helps you!
The theory
In order to Expand and Collapse a Java ITreeView, these are the steps:
Describe the ITreeView:
ITreeView treeView = Desktop.Describe<IWindow>(new WindowDescription())
.Describe<ITreeView>(new TreeViewDescription()
{
AttachedText = "Etc"
});
Get one of it's nodes:
ITreeViewNode treeViewNode = treeView.GetNode("someNode");
Expand or collapse it:
treeViewNode.Expand(); treeViewNode.Collapse();
The only expandable object is the ITreeViewNode (that is, this is the one that has the .Expand method), and the only way to get to the node is via an ITreeView, as shown above.
In practice...
You want to take an UIObject (I suspect this is what object identification center identified, right?) as an ITreeView, so that you can call Expand and Collapse on it?
This is not possible.
In these SDKs, every description is a generic element. In Java it's a UIObject, in Web it's a WebElement, etc..
Those more unique, like a tree view, extend the generic one (UIObject) and adds one more identification property in the process
In the ITreeView case, most probably it's the UIObject with NativeClass set as "javax.swing.JTree"
If Object Identification Center didn't identify the expandable object as an ITreeView, it's because it isn't.
Workaround
If your goal is just to expand, and manually double clicking works, then you can:
Identify the UIObject;
Calculate the coordinates of the point you manually double click, relative to the upper left pixel of the UIObject. (E.G. 5 pixels down, 5 to the right)
You can approximate it, go for a try and error, or use tools that can help.
Use HP.LFT.SDK.Mouse class to double click that exact location:
var loc = theUiButton.AbsoluteLocation;
var p = new System.Drawing.Point(loc.X + 5, loc.Y + 5);
Mouse.DoubleClick(p, MouseButton.Left);
The reason why it doesn't work right now is because .DoubleClick, by default, performs double click in the center of the UIObject, and I have a hunch that's not where the expandable object is - so you need to provide fine tuning parameters.
I'm writing a script editor that needs to transform the user's input (which is similar to script syntax) into valid C# according to some rules I define. For example, if the users puts in
using System;
public string hello()
{
return "Hi!" // whoops, semicolon here is missing!
}
I'd need to transform this to
using System;
public class ContainerClass
{
public string hello()
{
return "Hi!" // whoops, semicolon here is missing!
}
}
my transformation will insert new nodes (such as the class declaration) and might move around existing ones, but it will never modify or remove existing ones. (I know SourceCodeKind = Script does something vaguely similar, but I can't use that for a variety of reasons).
Now I need to come up with a way to do this transformation given the following considerations:
Since I need to run the transformation each time the user changes the original document (i.e. just types a single letter), I can't afford to re-parse the entire thing every time from a performance perspective. For example, if the user inserts the missing semicolon after ";", ideally I would just insert the same (or cloned) node into my already transformed document, instead of re-parsing everything. I suppose that rules out standard ways of modification such as DocumentEditor.
I need to have a way to re-map locations from my transformed document to locations in the original document. Since I will never delete nodes, I think theoretically this should be possible (but how?).
This is necessary e.g. as I would end up with diagnostic messages (and intellisense information etc.) pointing to locations in the transformed document, and need to get the original document's location for these to actually show them to the user.
Can anyone thing of a more or less direct way to do this? Is there maybe even some Roslyn helper classes for use cases like this?
My ideas below. I'm not quite sure they'd work, and I think they'd be very hard to implement, so I'm hoping there is some easier way to be honest;
For #1, my only idea was to get get the text changes (Document.GetTextChangesAsync) of the original document after its source code changes; and then somehow try to find out what nodes have been affected by this (maybe get nodes that intersect the edited area in the old and new document, then compute which ones have been deleted, added or modified) - and then to apply these changes in my transformed document. This seems awfully complex though.
For #2, my only idea so far was to enable tracking for nodes of the original document. Then I would find whatever node a location points to in the transformed document, and find the node in the original document this originated from (and then find the location of that node).
But the problem is that e.g. the code above would produce a diagnostic error pointing towards the location right after "Hi!", wich the location span's length of 0, as there's a semicolon missing. So the location doesn't really point to a node at all. Maybe I could try finding adjacent nodes in that case?!
I'm building an application where the user needs to be able to change the font colour and size (and the colour of the main camera), plus input an 'unlimited' amount of other data.
I'll admit that I've never used XML before, and am a relative beginner to C# as well. I've done a couple of days of searching on this topic already and the tutorials were all either writing from the wrong language or too vague in their explanations for me to completely understand what I was doing (I've included a list of the links I visited at the end of this question).
Ideally, I'd want to save to two locations, with the following format (apologies for the actual data - I'm not sure of the format some of it saves in):
Application.persistentDataPath, "prefs.xml"
<settings>
<font>
<colour>#FFFFFFFF</colour>
<size>14</size>
</font>
<camera>
<colour>#00000000</colour>
</camera>
</settings>
Application.persistentDataPath, "data.xml"
<data>
<section1>
<item1>
<date>01/01/2001</date>
<details>Details here</details>
<imagepath>Application.persistentDataPath, "image1.jpg"</imagepath>
</item1>
<item2>
<date>03/01/2001</date>
<details>Details here</details>
</item2>
</section1>
<section2>
<item1>
<date>02/01/2001</date>
<details>Details here</details>
</item1>
</section2>
</data>
To choose the colours, I'm using a package from the asset store called Color Picker and using two instances of this, with their game objects named as FontPicker and BackgroundPicker. I know that the next two scripts are completely over the place, but I'm putting them in as reference to what I've fiddled around with so far and what my variable names look like (script mainly edited using the YouTube video linked as reference). The code I currently have in my Settings.cs script is as follows:
public class Settings : MonoBehaviour {
public Color SelectedColor {get; set;}
public void Write (string fileName) {
using (var stream = new FileStream(fileName, FileMode.Create)) {
XmlSerializer XML = new XmlSerializer(typeof(ColorPicker));
XML.Serialize(stream, this);
}
}
public void FontColour () {
var FontPicker = GameObject.Find("FontPicker").GetComponent(ColorPicker);
// I get the following compiler errors in the Unity editor:
// Assets/Scripts/Settings.cs(23,77): error CS0119: Expression denotes a `type', where a `variable', `value' or `method group' was expected
// Assets/Scripts/Settings.cs(23,64): error CS1502: The best overloaded method match for `UnityEngine.GameObject.GetComponent(System.Type)' has some invalid arguments
// Assets/Scripts/Settings.cs(23,64): error CS1503: Argument `#1' cannot convert `object' expression to type `System.Type'
Color = ColorPicker.SelectedColor;
// Assets/Scripts/Settings.cs(24,37): error CS0120: An object reference is required to access non-static member `ColorPicker.SelectedColor'
// These same compiler errors are present in the next function
}
public void BackgroundColour () {
var BackgroundPicker = GameObject.Find("BackgroundPicker").GetComponent(ColorPicker);
Color = ColorPicker.SelectedColor;
}
}
I was hoping I'd be able to write to XML from within these separate functions so that I can call them individually when a colour is selected, but I have no idea where everything's meant to go and which bit of code is meant to be in which script.
From the YouTube video linked below, I also have this script, which ties in in some way:
[XmlRoot ("Preferences")]
public class XMLSerialiser {
[XmlArray ("Font"), XmlArrayItem("Colour")]
public Settings[] Settings;
public void Write(string path){
var serialiser = new XmlSerializer(typeof(XMLSerialiser));
using (var stream = new FileStream(path, FileMode.Create)) {
serialiser.Serialize(stream, this);
}
}
public static Settings Read(string path) {
var serialiser = new XmlSerializer(typeof(XMLSerialiser));
using (var stream = new FileStream(path, FileMode.Open)) {
return serialiser.Deserialize(stream) as XMLSerialiser;
}
}
}
What I'd be very grateful for is if someone could either point me in the direction of a good tutorial or explain to me, in very simple terms, how I would go about doing this - what I would have to write, which script I'd have to write it in, and where I'd have to put it in the Unity Editor. I'm very sorry my explanation took up as much space as it did.
Links used:
How to store and retrieve objects using XML?
http://unitynoobs.blogspot.co.uk/2011/02/xml-loading-data-from-xml-file.html
XML Data management in .NET
https://www.youtube.com/watch?v=zAn-ZbJqS90
http://wiki.unity3d.com/index.php?title=Saving_and_Loading_Data%3A_XmlSerializer
http://unitynoobs.blogspot.co.uk/2011/04/xml-writing-to-existing-xml-file.html
Append XML string block to existing XmlDocument
How can I build XML in C#?
Part of what I'm getting confused with is the fact that many of these links seems to use completely different methods to go about it. Take a look at the following two links, for example:
Writing a XML file to iOS with Unity (Using C#)
How to write an XML file in C# in Unity?
Is there a difference between these methods? Is one more preferable than the other?
So I managed to wrap my head around where I wanted things to go, thanks to a few more days of research (and especially this link).
I'm answering my own question so it may help someone else who has the same problem (and because I guess it's a bit silly to leave it unanswered now I have the solution).
To get around the issue of overwriting things, I used the method shown in the link, making an if loop to check whether the file existed. In the first section (the case where the file does not already exist), I used xmlWriter to put things together. XmlWriter appears to be a lot simpler than the method I use in the other section, with the downside that you don't seem to be able to use it without overwriting everything (hence only using it when the file doesn't already exist).
In the case that the file does exist, xDocument is brilliant. I modified the code from the link so that it replaced elements rather than adding new ones (although that can still be done). For this to work, include system.linq and system.xml (possibly system.xml.linq as well). Here is an example:
// Loads the document
XDocument xDocument = XDocument.Load("document.xml");
// Specifies the encoding
new XDeclaration("1.0","utf-8","yes");
// Follows the route settings-font and replaces the current colour
// element with a new one
xDocument.Element("Settings").Element("Font").Element("Colour").ReplaceWith(new XElement("Colour",fontColourHex));
// As above
xDocument.Element("Settings").Element("Background").Element("Colour").ReplaceWith(new XElement("Colour",backgroundColourHex));
// One of the more important things to note is that everything saved in an
// element ought to be a string, and the xml file doesn't like element
// names with spaces in them (use an underscore instead)
xDocument.Element("Settings").Element("Font").Element("Size_Modifier").ReplaceWith(new XElement("Size_Modifier",globalFontSizeModifier.ToString()));
// Saves the document
xDocument.Save("document.xml");
If you're wondering why there is no whitespace between words in this code, it's because, for some reason, Mono doesn't like it. I did find a fix somewhere else for it, but I couldn't get it to work and deleting the whitespace does the job fine.
The resultant file looked like this:
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<Font>
<Colour>FFFFFFFF</Colour>
<Size_Modifier>0</Size_Modifier>
</Font>
<Background>
<Colour>000000FF</Colour>
</Background>
</Settings>
After I modified it and tied it to a couple of functions, all I had to do was create a new game object with the script attached and use it as needed.
We have a circumstance where we basically want to generate string representation of a code file by passing in arguments for the template contents and information the template needs to build itself:
//*** PSEUDO CODE *** //
//loaded from an embedded resource file in a .dll. no physical file on file system
string templateContents = ...;
//has properties used by the template
object complexParameter = ...;
string generatedCode = generator.MakeCode(templateContents, complexParameter);
However, we're currently running into problems trying to get the T4 template generation to do what we want. The actual code we're using is:
var templatingEngine = new Engine();
//T4TextTemplateHost is our own class implementing ITextTemplatingEngineHost & IServiceProvider
var templateHost = new T4TextTemplateHost(references, imports)
{
Properties = parameters,
//this is supposed to be a file path? the generation bombs if this is left null
TemplateFile = "Dummy Value"
};
var templateContents = GetTemplateFileContents();
var retVal = templatingEngine.ProcessTemplate(templateContents, templateHost);
//if a CompilerError occurs, we get NO code, just a "ErrorGeneratingOutput" message
foreach (CompilerError error in templateHost.Errors)
//this information is pretty worthless: a compile error with line number for a
//non-existant code file
retVal += String.Format("{0}{2}Line: {1}{2}{2}", error.ErrorText,
error.Line, Environment.NewLine);
The problem is that the code generator seems to expect a physical file somewhere, and when things go wrong, we don't get code back, we get useless error messages back. It is our strong preference not NOT have the code automatically compiled, especially when the generated code has an error (we want a full, broken file to examine when troubleshooting).
We also want the output as a string, that we can take and do with whatever we wish.
Is there a way to make T4 code generation work more like the pseudo code example? We're on the verge of abandoning the T4 tool in favor of something like CodeSmith because T4 seems like it's too limited/geared toward a very specific way of managing templates and processing output.
I don't think it is possible to get T4 to generate anything if there are errors in the template you pass in. T4 will try to convert your template into codedom with extra statements that write out to a stringwriter, the final stringwriter is then returned as the result. If there are any errors in the template, the code will not complie and thus it will have nothing to return to you. The errors you get back should resolve to the lines in the template you passed in, at least that has been my experience.
I am not sure if Code Smith works in a different way but depending on the complexity of what you are trying to render you might have some luck using Nustache if it's simple enough. It's a dot net version of mustache templates. It supports basic looping and if/then type control blocks. I have successfully used it with embedded text files to generate simple templates for emailing and reports.
I'm looking for a way to read the contents of a method/function call, something similar to the Visual Studio 'Go To Implementation' functionality (or F11 in debug) but using code.
For example, if I had a C# .cs file that contained the following code:
var characters = "AAAAAABBBBBBCCCCCCDDDDDD";
var position = 0;
var newPosition;
//Example #1
myObject.FirstSixCharacters = characters.SubString(position, 6);
position += 6;
//Example #2
myObject.SecondSetOfSixCharacters = AStaticObject.AMethod(characters, position, 6, out newPosition);
position += newPosition;
I'd like to know if there is a way to programatically 'drop' into the 'AMethod' method of the 'AStaticObject' object and read the code?
To put the question into context, I have a large enterprise solution that in essence - parses a very large string of characters into lots of DTO fields. I'm thinking of writing something that will open a 'parser' .cs file, read the contents of the code then determine the position and length of each DTO field based upon the processing applied at the time of the assignment.
This is fairly straight forward to achieve for statements where the field assignment is the result of a simple 'substring' function (as per example #1, above). But on occassion the code uses static objects/methods to extract the characters of the string and update the positional variable (as per example #2, above). This led me to the question - "how can I, programmatically, read the code within the .cs file and when necessary follow calls made to objects/methods when they appear within the code and read the contents of those .cs file"?
Note: The code that I'm attempting to analyse is un-compiled, so decompiliers are not necessary to achieve the result.