I've written a working Language Service for the scripting language we use internally, but as I was figuring everything out as I went along I'm now looking back at it and adding more features and improvements and I've realized I've done a number of things that are potentially dangerous.
First off, I've been storing the results (methods, types, etc) of my parses inside my custom AuthoringScope subclass. I was saving the AuthoringScope object inside of the Source object that corresponded to the file that the ParseRequest requested. While this probably isn't terrible, the documentation makes it pretty clear that Source objects must only be accessed from the UI thread. Since ParseSource() is called on a background thread, I've since changed it so that the AuthoringScope results from the parse are kept track of by the LanguageService itself.
Is this a good way of keeping track of the results of a ParseReason.Check request? Currently, whenever I receive one of those requests I'm dumping my previous results and parsing the entire file again. I do keep track of the last fully successful parse and merge what I can if the current parse fails. I'm saving them off this way so that when later ParseSource() calls come through I've got immediate access to the parsed data and I can set some fields on my AuthoringScope object that will be used to return the correct data once ParseSource() completes.
And now to the second part of the question: In some of my ParseSource() code, for example when I'm matching braces and when trying to determine what text is before the ParseRequest's line and column, after tokenizing the ParseRequest's text buffer I'm then using the IVsTextLines in order to do things like convert line and column values into a position index into the text buffer, and to simplify getting the lengths of lines and other information. I realize this is very, very bad since I'm messing with an object that the documentation mentions multiple times should NOT be touched from a non-UI thread. Is there another object I can feed the text buffer into that will give me the same convenient access to that information? Or in this case am I going to have to roll my own code to do such conversions and query the data I'm looking for?
For what it's worth, I also use a lot of the IVsTextLines functionality inside my AuthoringScope's methods, but I'm fairly certain those methods are being called from the UI thread and my usage there should be safe.
Thanks in advance for any ideas! I have a feeling that I may be able to change the code that tokenizes the input text to have it calculate both start and end indices into the text buffer as well as creating a TextSpan for that range if I scan through the text and keep track of that data as I go along. If I go that route, I should also be able to cache line lengths and such to allow me to do much of what I'm currently using IVsTextLines for now. It just feels like there ought to be a way to still utilize IVsTextLines' functionality without having to rewrite it all by hand, though...