vsintegration/src/FSharp.LanguageService.Base/LanguageService.cs (1,311 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Shell; using Microsoft.Win32; using System; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Forms; using System.Xml; using System.Security.Permissions; using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; using IServiceProvider = System.IServiceProvider; using ShellConstants = Microsoft.VisualStudio.Shell.Interop.Constants; using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants; using VsShell = Microsoft.VisualStudio.Shell.VsShellUtilities; using Microsoft.VisualStudio.FSharp.LanguageService.Resources; namespace Microsoft.VisualStudio.FSharp.LanguageService { internal enum BackgroundRequestReason { MemberSelect, // ".", also triggered by some some other tokens like ".." MemberSelectAndHighlightBraces, // unused? No F# tokens have both MatchBraces and MemberSelect MatchBracesAndMethodTip, // close-paren MatchBraces, // moving cursor etc. FullTypeCheck, // triggered on idle CompleteWord, //Ctrl-space DisplayMemberList, //Ctrl-J QuickInfo, // mouse hover MethodTip, // open-paren, close-paren or comma Goto, // F12 /// <summary> /// This reason is used when we want to trigger only 'parsing' (without type-checking) to /// update the untyped AST information (e.g. when a different file is opened). After updating /// the untyped scope (in F# LS), a 'null' can be returned as the result of 'ExecuteBackgroundRequest'. /// </summary> ParseFile }; [CLSCompliant(false), ComVisible(true)] // // Note: Tests using this code should either be adjusted to test the corresponding feature in // FSharp.Editor, or deleted. However, the tests may be exercising underlying F# Compiler // functionality and thus have considerable value, they should ony be deleted if we are sure this // is not the case. // public abstract class LanguageService_DEPRECATED : IDisposable, IVsLanguageContextProvider, IOleServiceProvider, IObjectWithSite, IVsDebuggerEvents, IVsFormatFilterProvider, ILanguageServiceTestHelper { private IServiceProvider site; private ArrayList codeWindowManagers; private LanguagePreferences preferences; internal ArrayList sources; private bool disposed; private IVsDebugger debugger; private uint cookie; private DBGMODE dbgMode; private int lcid; private bool isServingBackgroundRequest; // used to stop the OnIdle thread making new background requests when a request is already running protected LanguageService_DEPRECATED() { this.codeWindowManagers = new ArrayList(); this.sources = new ArrayList(); } internal abstract void Initialize(); internal IServiceProvider Site { get { return this.site; } } internal LanguagePreferences Preferences { get { if (this.preferences == null && !disposed) { this.preferences = this.GetLanguagePreferences(); } return this.preferences; } set { this.preferences = value; } } /// <summary> /// Cleanup the sources, uiShell, shell, preferences and imageList objects /// and unregister this language service with VS. /// </summary> public virtual void Dispose() { OnActiveViewChanged(null); this.disposed = true; this.StopBackgroundThread(); this.lastActiveView = null; if (this.sources != null) { foreach (ISource s in this.sources) { s.Dispose(); } this.sources.Clear(); this.sources = null; } if (this.codeWindowManagers != null) { foreach (CodeWindowManager m in this.codeWindowManagers) { m.Close(); } this.codeWindowManagers.Clear(); this.codeWindowManagers = null; } if (this.preferences != null) { this.preferences.Dispose(); this.preferences = null; } if (this.debugger != null && this.cookie != 0) { NativeMethods.ThrowOnFailure(this.debugger.UnadviseDebuggerEvents(this.cookie)); this.cookie = 0; this.debugger = null; } this.site = null; } // Methods implemented by subclass. /// It is expected that you will have one static language preferences object /// for your package. internal abstract LanguagePreferences GetLanguagePreferences(); internal abstract void ExecuteBackgroundRequest(BackgroundRequest_DEPRECATED req); /// If this returns true we can reuse a recent IntellisenseInfo if its available internal abstract bool IsRecentScopeSufficientForBackgroundRequest(BackgroundRequestReason req); internal Guid GetLanguageServiceGuid() { return this.GetType().GUID; } // Provides context from the language service to the Visual Studio core editor. int IVsLanguageContextProvider.UpdateLanguageContext(uint dwHint, IVsTextLines buffer, TextSpan[] ptsSelection, object ptr) { if (ptr != null && ptr is IVsUserContext && buffer is IVsTextBuffer) return UpdateLanguageContext((LanguageContextHint)dwHint, buffer, ptsSelection, (IVsUserContext)ptr); else return NativeMethods.E_FAIL; } /// <summary> /// Call this method if you want UpdateLanguageContext to be called again. /// </summary> internal void SetUserContextDirty(string fileName) { if (string.IsNullOrEmpty(fileName)) return; IVsWindowFrame windowFrame = null; uint itemID = Microsoft.VisualStudio.VSConstants.VSITEMID_NIL; IVsUIHierarchy hierarchy = null; if (VsShell.IsDocumentOpen(this.Site, fileName, Guid.Empty, out hierarchy, out itemID, out windowFrame)) { IVsUserContext context; if (windowFrame != null) { object prop; int hr = windowFrame.GetProperty((int)__VSFPROPID.VSFPROPID_UserContext, out prop); context = (IVsUserContext)prop; if (NativeMethods.Succeeded(hr) && context != null) { context.SetDirty(1); } } } } internal virtual int UpdateLanguageContext(LanguageContextHint hint, IVsTextLines buffer, TextSpan[] ptsSelection, IVsUserContext context) { // From the docs: Any failure code: means the implementer is "passing" on this opportunity to provide context and the text editor will fall back to other mechanisms. if (ptsSelection == null || ptsSelection.Length != 1) return NativeMethods.E_FAIL; context.RemoveAttribute(null, null); TextSpan span = ptsSelection[0]; IVsTextLines lastActiveBuffer; IVsTextView lastAciveView = this.LastActiveTextView; if (lastActiveView == null) return NativeMethods.E_FAIL; NativeMethods.ThrowOnFailure(lastActiveView.GetBuffer(out lastActiveBuffer)); if (lastActiveBuffer != buffer) return NativeMethods.E_FAIL; ISource source = GetSource(buffer); if (source == null) return NativeMethods.E_FAIL; var req = source.BeginBackgroundRequest(span.iStartLine, span.iStartIndex, new TokenInfo(), BackgroundRequestReason.FullTypeCheck, lastActiveView, RequireFreshResults.Yes, new BackgroundRequestResultHandler(this.HandleUpdateLanguageContextResponse)); if (req == null || req.Result == null) return NativeMethods.E_FAIL; if ((req.IsSynchronous || ((req.Result != null) && req.Result.TryWaitForBackgroundRequestCompletion(1000)))) { if (req.IsAborted) return NativeMethods.E_FAIL; if (req.ResultIntellisenseInfo != null) { req.ResultIntellisenseInfo.GetF1KeywordString(span, context); return NativeMethods.S_OK; } } else // result is asynchronous and have not completed within 1000 ms { context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "devlang", "fsharp"); context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_LookupF1_CaseSensitive, "keyword", "fsharp.typechecking.incomplete"); return NativeMethods.S_OK; } return NativeMethods.E_FAIL; } internal void HandleUpdateLanguageContextResponse(BackgroundRequest_DEPRECATED req) { } internal virtual ImageList GetImageList() { ImageList ilist = new ImageList(); ilist.ImageSize = new Size(16, 16); ilist.TransparentColor = Color.FromArgb(255, 0, 255); Stream stream = typeof(LanguageService_DEPRECATED).Assembly.GetManifestResourceStream("Resources.completionset.bmp"); ilist.Images.AddStrip(new Bitmap(stream)); return ilist; } internal bool IsMacroRecordingOn() { IVsShell shell = this.GetService(typeof(SVsShell)) as IVsShell; if (shell != null) { object pvar; NativeMethods.ThrowOnFailure(shell.GetProperty((int)__VSSPROPID.VSSPROPID_RecordState, out pvar)); shell = null; if (pvar != null) { return ((VSRECORDSTATE)pvar == VSRECORDSTATE.VSRECORDSTATE_ON); } } return false; } internal IVsDebugger GetIVsDebugger() { if (this.debugger == null) { Guid guid = typeof(Microsoft.VisualStudio.Shell.Interop.IVsDebugger).GUID; this.debugger = this.GetService(typeof(IVsDebugger)) as IVsDebugger; if (this.debugger != null) { NativeMethods.ThrowOnFailure(debugger.AdviseDebuggerEvents(this, out this.cookie)); DBGMODE[] mode = new DBGMODE[1]; NativeMethods.ThrowOnFailure(debugger.GetMode(mode)); this.dbgMode = mode[0]; } } return debugger; } internal IVsTextMacroHelper GetIVsTextMacroHelperIfRecordingOn() { if (IsMacroRecordingOn()) { IVsTextManager textmgr = (IVsTextManager)this.GetService(typeof(SVsTextManager)); return (IVsTextMacroHelper)textmgr; } return null; } internal void OpenDocument(string path) { VsShell.OpenDocument(this.site, path); } // State used for OnIdle synchronization of dropdown menu and other visual elements dependent on the active view internal int lastLine = -1; internal int lastCol = -1; internal string lastFileName; internal IVsTextView lastActiveView; // STATIC ROOT INTO PROJECT BUILD internal IntellisenseInfo_DEPRECATED recentFullTypeCheckResults = null; internal string recentFullTypeCheckFile = null; /// <devdoc> /// Returns the last active IVsTextView that is managed by this language service. /// </devdoc> internal IVsTextView LastActiveTextView { get { return this.lastActiveView; } } /// Returns the last active successful fetch of an IntellisenseInfo that is managed by this language service. /// This is only relevant to the active text view and is cleared each time the text view is switched. If it /// is null we must make a background request to the language service to get the recent full typecheck results. /// If a file is dirty, an OnIdle call will kick in to refresh the recent results. internal IntellisenseInfo_DEPRECATED RecentFullTypeCheckResults { get { return this.recentFullTypeCheckResults; } set { this.recentFullTypeCheckResults = value; } } internal string RecentFullTypeCheckFile { get { return this.recentFullTypeCheckFile; } set { this.recentFullTypeCheckFile = value; } } /// <devdoc> /// Return whether or not the last active text view is one of ours or not. /// </devdoc> internal bool IsActive { get { if (disposed) return false; if (this.lastActiveView == null) return false; return this.GetSource(this.lastActiveView) != null; } } internal virtual int OnIdle(bool periodic, IOleComponentManager mgr) { if (!this.IsActive) return 0; // here's our chance to synchronize combo's and so on, // first we see if the caret has moved. IVsTextView view = this.lastActiveView; if (view == null) return 0; ISource s = this.GetSource(view); if (s == null) return 0; int line = -1, col = -1; var hr = view.GetCaretPos(out line, out col); if (NativeMethods.Failed(hr)) return 0; if (line != this.lastLine || col != this.lastCol || this.lastFileName == null) { this.lastLine = line; this.lastCol = col; this.lastFileName = s.GetFilePath(); CodeWindowManager cwm = this.GetCodeWindowManagerForView(view); if (cwm != null) { this.OnCaretMoved(cwm, view, line, col); } } s.OnIdle(periodic); // do idle processing for currently-focused file bool moreToDo = false; #if CHECK_ALL_DIRTY_FILES_ON_PERIODIC_IDLE if (periodic && mgr.FContinueIdle() != 0) { // while there is spare idle time, pick a dirty file (if there is one) and do idle processing for it for (int i = 0; i < this.sources.Count; ++i) { Source so = this.sources[i] as Source; if (so != null && so.IsDirty) { so.OnIdle(periodic); if (mgr.FContinueIdle() == 0) { moreToDo = true; break; } } } } #endif return moreToDo ? 1 : 0; } internal abstract TypeAndMemberDropdownBars CreateDropDownHelper(IVsTextView forView); internal virtual void OnActiveViewChanged(IVsTextView textView) { this.lastActiveView = textView; this.lastFileName = null; this.recentFullTypeCheckResults = null; this.recentFullTypeCheckFile = null; } internal virtual void OnActiveViewLostFocus(IVsTextView textView) { FSharpSourceBase_DEPRECATED s = (FSharpSourceBase_DEPRECATED)this.GetSource(textView); if (s != null) s.HandleLostFocus(); } internal virtual void OnCaretMoved(CodeWindowManager mgr, IVsTextView textView, int line, int col) { if (mgr.DropDownHelper != null) mgr.DropDownHelper.SynchronizeDropdowns(textView, line, col); } internal virtual void SynchronizeDropdowns() { IVsTextView textView = this.LastActiveTextView; if (textView != null) { CodeWindowManager mgr = this.GetCodeWindowManagerForView(textView); if (mgr != null && mgr.DropDownHelper != null) { try { int line = -1, col = -1; if (NativeMethods.Failed(textView.GetCaretPos(out line, out col))) return; mgr.DropDownHelper.SynchronizeDropdowns(textView, line, col); } catch { } } } } protected virtual void OnChangesCommitted(uint flags, Microsoft.VisualStudio.TextManager.Interop.TextSpan[] ptsChanged) { } internal abstract Colorizer GetColorizer(IVsTextLines buffer); // We have to make sure we return the same colorizer for each text buffer, // so we keep a hashtable of IVsTextLines -> Source objects, the Source // object owns the Colorizer for that buffer. If this method returns null // then it means the text buffer does not belong to this language service. internal ISource GetSource(IVsTextLines buffer) { if (buffer == null) return null; foreach (ISource src in this.sources) { if (src.GetTextLines() == buffer) { return src; } } return null; } internal ISource GetSource(IVsTextView view) { if (view == null) return null; IVsTextLines buffer; NativeMethods.ThrowOnFailure(view.GetBuffer(out buffer)); return GetSource(buffer); } internal ISource GetSource(string fname) { if (this.sources != null) { foreach (ISource s in this.sources) { if (NativeMethods.IsSamePath(s.GetFilePath(), fname)) return s; } } return null; } internal virtual void OnCloseSource(ISource source) { // JAF: Consider using cancellation token to stop the real (non-MLS) background thread. // StopBackgroundThread(); if (this.sources != null) { if (this.sources.Contains(source)) { this.sources.Remove(source); } } } internal virtual bool IsSourceOpen(ISource src) { return (this.sources != null) && this.sources.Contains(src); } internal bool IsDebugging { get { if (this.debugger == null) { this.debugger = GetIVsDebugger(); } return this.dbgMode != DBGMODE.DBGMODE_Design; } } #if DOCUMENT_PROPERTIES // Override this method to create your own custom document properties for // display in the Properties Window when the editor for this Source is active. // Default is null which means there will be no document properties. internal virtual DocumentProperties CreateDocumentProperties(CodeWindowManager mgr) { return null; } #endif /// If the functionName is supported, return a new IVsExpansionFunction object. internal virtual ExpansionFunction CreateExpansionFunction(ExpansionProvider provider, string functionName) { return null; } internal virtual ExpansionProvider CreateExpansionProvider(ISource src) { return new ExpansionProvider(src); } internal virtual CodeWindowManager CreateCodeWindowManager(IVsCodeWindow codeWindow, ISource source) { return new CodeWindowManager(this, codeWindow, source); } internal object GetService(Type serviceType) { if (this.site != null) { return this.site.GetService(serviceType); } return null; } public virtual int QueryService(ref Guid guidService, ref Guid iid, out IntPtr obj) { obj = IntPtr.Zero; if (this.site != null) { IOleServiceProvider psp = this.GetService(typeof(IOleServiceProvider)) as IOleServiceProvider; if (psp != null) NativeMethods.ThrowOnFailure(psp.QueryService(ref guidService, ref iid, out obj)); return 0; } return (int)NativeMethods.E_UNEXPECTED; } // Override this method if you want to insert your own view filter // into the command chain. internal virtual ViewFilter CreateViewFilter(CodeWindowManager mgr, IVsTextView newView) { return new ViewFilter(mgr, newView); } internal void AddCodeWindowManager(CodeWindowManager m) { this.codeWindowManagers.Add(m); } internal void RemoveCodeWindowManager(CodeWindowManager m) { this.codeWindowManagers.Remove(m); } internal CodeWindowManager GetCodeWindowManagerForView(IVsTextView view) { if (view == null) return null; foreach (CodeWindowManager m in this.codeWindowManagers) { if (m.CodeWindow != null) { IVsTextView pView; int hr = m.CodeWindow.GetLastActiveView(out pView); if (hr == NativeMethods.S_OK && pView == view) return m; } } return null; } internal CodeWindowManager GetCodeWindowManagerForSource(ISource src) { if (src == null) return null; foreach (CodeWindowManager m in this.codeWindowManagers) { if (m.Source == src) { return m; } } return null; } /// <summary>Executes the given command if it is enabled and supported using the /// current SUIHostCommandDispatcher.</summary> internal int DispatchCommand(Guid cmdGuid, uint cmdId, IntPtr pvaIn, IntPtr pvaOut) { int hr = NativeMethods.E_FAIL; IOleCommandTarget cmdTarget = this.Site.GetService(typeof(SUIHostCommandDispatcher)) as IOleCommandTarget; if (cmdTarget != null) { OLECMD[] prgCmds = new OLECMD[1]; prgCmds[0].cmdID = cmdId; hr = cmdTarget.QueryStatus(ref cmdGuid, 1, prgCmds, IntPtr.Zero); if (hr == NativeMethods.S_OK && ((prgCmds[0].cmdf & (uint)OLECMDF.OLECMDF_ENABLED) == (uint)OLECMDF.OLECMDF_ENABLED)) { hr = cmdTarget.Exec(ref cmdGuid, cmdId, 0, IntPtr.Zero, IntPtr.Zero); } } return hr; } internal void ScrollToEnd(IVsWindowFrame frame) { IVsTextView view = VsShell.GetTextView(frame); if (view != null) { ScrollToEnd(view); } } internal void ScrollToEnd(IVsTextView view) { IVsTextLines buffer; NativeMethods.ThrowOnFailure(view.GetBuffer(out buffer)); int lines; NativeMethods.ThrowOnFailure(buffer.GetLineCount(out lines)); int lineHeight; NativeMethods.ThrowOnFailure(view.GetLineHeight(out lineHeight)); NativeMethods.RECT bounds = new NativeMethods.RECT(); NativeMethods.GetClientRect(view.GetWindowHandle(), ref bounds); int visibleLines = ((bounds.bottom - bounds.top) / lineHeight) - 1; // The line number needed to be passed to SetTopLine is ZERO based, so need to subtract ONE from number of total lines int top = Math.Max(0, lines - visibleLines - 1); Debug.Assert(lines > top, "Cannot set top line to be greater than total number of lines"); #if XMLTRACE Trace.WriteLine("ScrollToEnd: lines=" + lines + ", visibleLines=" + visibleLines + ", top=" + top); #endif NativeMethods.ThrowOnFailure(view.SetTopLine(top)); } internal BackgroundRequestAsyncResult_DEPRECATED BeginBackgroundRequest(BackgroundRequest_DEPRECATED request, BackgroundRequestResultHandler handler) { EnsureBackgroundThreadStarted(); lock (this) { request.Callback = handler; this.requests.Enqueue(request); this.isServingBackgroundRequest = true; this.backgroundRequestPending.Set(); this.backgroundRequestDone.Reset(); // Return a capability to wait on the completion of the background request return new BackgroundRequestAsyncResult_DEPRECATED(request, this.backgroundRequestDone); } } internal BackgroundRequest_DEPRECATED CreateBackgroundRequest(FSharpSourceBase_DEPRECATED s, int line, int idx, TokenInfo info, string sourceText, ITextSnapshot snapshot, MethodTipMiscellany_DEPRECATED methodTipMiscellany, string fname, BackgroundRequestReason reason, IVsTextView view) { // We set this to "false" because we are effectively abandoning any currently executing background request, e.g. an OnIdle request this.isServingBackgroundRequest = false; bool sync = false; if (!this.Preferences.EnableAsyncCompletion) { sync = true; //unless registry value indicates that sync ops always prefer async } return CreateBackgroundRequest(line, idx, info, sourceText, snapshot, methodTipMiscellany, fname, reason, view, s.CreateAuthoringSink(reason, line, idx), s, s.ChangeCount, sync); } // Implemented in FSharpLanguageService.fs internal abstract BackgroundRequest_DEPRECATED CreateBackgroundRequest(int line, int col, TokenInfo info, string sourceText, ITextSnapshot snapshot, MethodTipMiscellany_DEPRECATED methodTipMiscellany, string fname, BackgroundRequestReason reason, IVsTextView view,AuthoringSink sink, ISource source, int timestamp, bool synchronous); // Implemented in FSharpLanguageService.fs internal abstract void OnParseFileOrCheckFileComplete(BackgroundRequest_DEPRECATED req); internal void EnsureBackgroundThreadStarted() { if (this.backgroundThread == null && !disposed) { this.backgroundRequestPending = new ManualResetEvent(false); this.backgroundThreadTerminated = new ManualResetEvent(false); this.backgroundRequestDone = new ManualResetEvent(false); this.backgroundThread = new Thread(new ThreadStart(BackgroundRequestThread)); this.backgroundThread.Start(); } } internal void StopBackgroundThread() { if (this.backgroundThread != null) { requests.Set(new BackgroundRequest_DEPRECATED(true)); ManualResetEvent ptt = this.backgroundThreadTerminated; this.backgroundRequestPending.Set(); if (!ptt.WaitOne(10, false)) { // give it a few milliseconds... // Then kill it right away so devenv.exe shuts down quickly and so that // the parse thread doesn't try to access services that are already shutdown. try { this.backgroundThread.Abort(); this.backgroundRequestDone.Set(); // make sure this gets set! } catch { } this.backgroundThread = null; } } CleanupThread(); } internal void CleanupThread() { this.backgroundRequestPending = null; this.backgroundThreadTerminated = null; this.backgroundThread = null; this.backgroundRequestDone = null; } internal bool IsServingBackgroundRequest { get { return this.isServingBackgroundRequest; } } internal PendingRequests_DEPRECATED requests = new PendingRequests_DEPRECATED(); internal ManualResetEvent backgroundRequestPending; internal ManualResetEvent backgroundThreadTerminated = new ManualResetEvent(false); private ManualResetEvent backgroundRequestDone; internal Thread backgroundThread; internal void BackgroundRequestThread() { try { // Initialize this thread's culture info with that of the shell's LCID Thread.CurrentThread.CurrentUICulture = new CultureInfo(this.lcid); bool stop = false; while (!stop) { if (!backgroundRequestPending.WaitOne(10000, true)) { break; } BackgroundRequest_DEPRECATED req = null; lock (this) { req = this.requests.Dequeue(); backgroundRequestPending.Reset(); } if (req.Terminate) break; try { // Ensure that no new OnIdle requests are issued during the execution of this request this.isServingBackgroundRequest = true; this.ExecuteBackgroundRequest(req); // If another parse request has already come in then the // user must be typing really fast (e.g. macros) and // so we throw this response away, and go right on to the // next request. // Note this must be asynchronous (do NOT call invoke). // Reason being that the UI thread may then want to call // StopBackgroundThread, which would deadlock if this was synchronous. if (!requests.ContainsSimilarRequest(req) || req.Reason == BackgroundRequestReason.FullTypeCheck) { UIThread.Run( delegate() { req.Callback(req); } ); } } catch (ThreadAbortException) { stop = true; } catch { // prevent a stray exception from ending the LS processing thread; without this we'll exit the while loop and // this thread will quietly exit and there will no longer be a LS running } finally { if (this.backgroundRequestDone != null) //thread cleanup might have set this to null { this.backgroundRequestDone.Set(); } } this.isServingBackgroundRequest = false; } ManualResetEvent ptt = backgroundThreadTerminated; CleanupThread(); ptt.Set(); this.isServingBackgroundRequest = false; } catch { // final exception handler for the thread, to make sure the whole VS process does not come down due to stray exception #if LANGTRACE Trace.WriteLine("Background Parse Thread Aborted"); #endif } this.isServingBackgroundRequest = false; } public void GetSite(ref Guid iid, out IntPtr ptr) { IntPtr pUnk = Marshal.GetIUnknownForObject(this.site); try { Marshal.QueryInterface(pUnk, ref iid, out ptr); } finally { // This is to release the reference from GetIUnknownForObject. // There may be an additional reference from the QueryInterface that will be owned by the caller of GetSite Marshal.Release(pUnk); } } public void SetSite(object site) { if (site is IServiceProvider) { this.site = (IServiceProvider)site; } else if (site is IOleServiceProvider) { this.site = new Microsoft.VisualStudio.Shell.ServiceProvider ((IOleServiceProvider)site); } Microsoft.VisualStudio.Shell.Package pkg = (Microsoft.VisualStudio.Shell.Package)this.site.GetService(typeof(Microsoft.VisualStudio.Shell.Package)); this.lcid = pkg.GetProviderLocale(); } public virtual int OnModeChange(DBGMODE dbgmodeNew) { this.dbgMode = dbgmodeNew; return NativeMethods.S_OK; } /// Return true if the given encoding information is invalid for your language service /// Default always returns false. If you return true, then also return an error /// message to display to the user. internal virtual bool QueryInvalidEncoding(__VSTFF format, out string errorMessage) { errorMessage = null; return false; } // Provides the list of available extensions for Save As. // The following default filter string is automatically added // by Visual Studio: // "All Files (*.*)\n*.*\nText Files (*.txt)\n*.txt\n" internal abstract string GetFormatFilterList(); // Provides the index to the filter matching the extension of the file passed in. internal abstract int CurFileExtensionFormat(string fileName); int IVsFormatFilterProvider.QueryInvalidEncoding(uint format, out string pbstrMessage) { if (QueryInvalidEncoding((__VSTFF)format, out pbstrMessage)) { return NativeMethods.S_OK; } return NativeMethods.S_FALSE; } int IVsFormatFilterProvider.CurFileExtensionFormat(string bstrFileName, out uint pdwExtnIndex) { pdwExtnIndex = 0; if (!string.IsNullOrEmpty(bstrFileName)) { int i = CurFileExtensionFormat(bstrFileName); if (i >= 0) { pdwExtnIndex = (uint)i; return NativeMethods.S_OK; } } return NativeMethods.E_FAIL; // return 0 - but no match found. } int IVsFormatFilterProvider.GetFormatFilterList(out string pbstrFilterList) { pbstrFilterList = GetFormatFilterList(); if (pbstrFilterList.Contains("|")) { string[] sa = pbstrFilterList.Split('|'); pbstrFilterList = string.Join("\n", sa); } if (pbstrFilterList == null) return NativeMethods.E_FAIL; // Must be terminated with a new line character. // (since inside VS this results in the proper Win32 saveas dialog double null // termination format since the new lines are replaced with nulls). // (See dlgsave.cpp line 163 in the InvokeSaveAsDlg function). if (!pbstrFilterList.EndsWith("\n", StringComparison.OrdinalIgnoreCase)) pbstrFilterList = pbstrFilterList + "\n"; return NativeMethods.S_OK; } /// <summary> /// Version number will increment to indicate a change in the semantics of preexisting methods. /// </summary> int ILanguageServiceTestHelper.GetSemanticsVersion() { return 3; } } // end class LanguageService internal class BackgroundRequestAsyncResult_DEPRECATED { ManualResetEvent globalRequestCompletedEvent; BackgroundRequest_DEPRECATED req; internal BackgroundRequestAsyncResult_DEPRECATED(BackgroundRequest_DEPRECATED req, ManualResetEvent globalRequestCompletedEvent) { this.globalRequestCompletedEvent = globalRequestCompletedEvent; this.req = req; } internal bool TryWaitForBackgroundRequestCompletion(int millisecondsTimeout) { return this.globalRequestCompletedEvent.WaitOne(millisecondsTimeout, false); } } internal delegate void BackgroundRequestResultHandler(BackgroundRequest_DEPRECATED request); internal enum MethodTipMiscellany_DEPRECATED { Typing, // OnCommand TYPECHAR nothing special refresh already-displayed tip ExplicitlyInvokedViaCtrlShiftSpace, // ViewFilter PARAMINFO JustPressedBackspace, // OnCommand BACKSPACE JustPressedOpenParen, // OnCommand TYPECHAR TokenTriggers ParamStart JustPressedComma, // OnCommand TYPECHAR TokenTriggers ParamNext JustPressedCloseParen, // OnCommand TYPECHAR TokenTriggers ParamEnd } internal class BackgroundRequest_DEPRECATED { int line, col; ISource source; TextSpan dirtySpan; string fileName; string text; BackgroundRequestReason reason; IVsTextView view; ITextSnapshot snapshot; bool terminate; BackgroundRequestResultHandler callback; AuthoringSink sink; IntellisenseInfo_DEPRECATED scope; bool isFreshFullTypeCheck; int startTimeForOnIdleRequest; string quickInfoText; TextSpan quickInfoSpan; TokenInfo tokenInfo; int timestamp; int resultTimestamp; RequireFreshResults requireFreshResults; bool isSynchronous; internal BackgroundRequestAsyncResult_DEPRECATED result; internal MethodTipMiscellany_DEPRECATED MethodTipMiscellany { get; set; } internal RequireFreshResults RequireFreshResults { get { return requireFreshResults; } set { requireFreshResults = value; } } internal bool IsSynchronous { get { return isSynchronous; } set { isSynchronous = value; } } internal BackgroundRequestAsyncResult_DEPRECATED Result { get { return result; } } internal int Line { get { return this.line; } set { this.line = value; } } internal int Col { get { return this.col; } set { this.col = value; } } internal TextSpan DirtySpan { get { return this.dirtySpan; } set { this.dirtySpan = value; } } internal string FileName { get { return this.fileName; } set { this.fileName = value; } } internal string Text { get { return this.text; } set { this.text = value; } } internal BackgroundRequestReason Reason { get { return this.reason; } set { this.reason = value; } } internal IVsTextView View { get { return this.view; } set { this.view = value; } } internal ITextSnapshot Snapshot { get { return this.snapshot; } set { this.snapshot = value; } } internal bool Terminate { get { return this.terminate; } set { this.terminate = value; } } internal BackgroundRequestResultHandler Callback { get { return this.callback; } set { this.callback = value; } } internal AuthoringSink ResultSink { get { return this.sink; } set { this.sink = value; } } internal IntellisenseInfo_DEPRECATED ResultIntellisenseInfo { get { return this.scope; } set { this.scope = value; } } internal bool ResultClearsDirtinessOfFile { get { return this.isFreshFullTypeCheck; } set { this.isFreshFullTypeCheck = value; } } internal int StartTimeForOnIdleRequest { get { return this.startTimeForOnIdleRequest; } set { this.startTimeForOnIdleRequest = value; } } internal string ResultQuickInfoText { get { return this.quickInfoText; } set { this.quickInfoText = value; } } internal TextSpan ResultQuickInfoSpan { get { return this.quickInfoSpan; } set { this.quickInfoSpan = value; } } internal TokenInfo TokenInfo { get { return this.tokenInfo; } set { this.tokenInfo = value; } } internal int Timestamp { get { return this.timestamp; } set { this.timestamp = value; } } /// File timestamp (ChangeCount) that the results correspond to. This will be different than Timestamp in the case /// that stale result were used. internal int ResultTimestamp { get { return this.resultTimestamp; } set { this.resultTimestamp = value; } } internal BackgroundRequest_DEPRECATED(bool terminate) { this.Terminate = terminate; } /// <summary> /// Source that represents the opened file for which the parse request was created. /// This can be used for accessing information like file name, line lengths etc. /// </summary> internal ISource Source { get { return this.source; } set { this.source = value; } } internal bool IsAborted { get; set; } internal BackgroundRequest_DEPRECATED(int line, int col, TokenInfo info, string src, ITextSnapshot snapshot, MethodTipMiscellany_DEPRECATED methodTipMiscellany, string fname, BackgroundRequestReason reason, IVsTextView view, AuthoringSink sink, ISource source, int timestamp, bool synchronous) { this.Source = source; this.Timestamp = timestamp; this.Line = line; this.Col = col; this.FileName = fname; this.Text = src; this.Reason = reason; this.View = view; this.Snapshot = snapshot; this.MethodTipMiscellany = methodTipMiscellany; this.ResultSink = sink; this.TokenInfo = info; this.isSynchronous = synchronous; this.ResultIntellisenseInfo = null; this.ResultClearsDirtinessOfFile = false; } } /// <summary> /// Represents result returned from scope.Goto /// If Success = true, then Url\Span should be filled, ErrorDescription will be null /// If Success = false - then only ErrorDescription will have value, Url and Span will have default values /// </summary> internal class GotoDefinitionResult_DEPRECATED { private GotoDefinitionResult_DEPRECATED(bool success, string url, TextSpan span, string errorDescription) { Success = success; Url = url; ErrorDescription = errorDescription; Span = span; } public bool Success { get; private set; } public string Url { get; private set; } public TextSpan Span { get; private set; } public string ErrorDescription { get; private set; } /// <summary> /// Creates instance of GotoDefinitionResult that will have Success = true /// </summary> /// <param name="url">Path to source file</param> /// <param name="span">Location in source file</param> /// <returns>New instance of GotoDefinitionResult</returns> public static GotoDefinitionResult_DEPRECATED MakeSuccess(string url, TextSpan span) { if (url == null) throw new ArgumentNullException("url"); return new GotoDefinitionResult_DEPRECATED(true, url, span, null); } /// <summary> /// Creates instance of GotoDefinitionResult that will have Success = false /// </summary> /// <param name="errorDescription">Error message</param> /// <returns>New instance of GotoDefinitionResult</returns> public static GotoDefinitionResult_DEPRECATED MakeError(string errorDescription) { if (String.IsNullOrWhiteSpace(errorDescription)) throw new ArgumentNullException("errorDescription"); return new GotoDefinitionResult_DEPRECATED(false, null, default(TextSpan), errorDescription); } } /// <summary> /// Stores incoming requests /// Maintains queue using following rules /// 1. Max amount of items in queue = 2 /// 2. requests are divided to UI and non-UI /// 3. UI request discards all requests that were enqueued before /// 4. non-UI request replaces old non-UI request that was enqueued before /// 5. if non-UI request is enqueued after UI request -> nothing happens and they will be dequeued subsequently /// /// </summary> internal class PendingRequests_DEPRECATED { private enum RequestType { Ui, NonUi } private readonly object syncRoot = new object(); // invariants: (first == null && second == null) || (first != null && second == null) || (first != null && second != null) // first == null && second == null => count == 0 // first != null && second == null => count == 1 // first != null && second != null => count == 2 private BackgroundRequest_DEPRECATED first; private BackgroundRequest_DEPRECATED second; public void Enqueue(BackgroundRequest_DEPRECATED newRequest) { if (newRequest == null) throw new ArgumentNullException("newRequest"); lock (syncRoot) { if (first == null) { // 0-items // just add request to the queue first = newRequest; } else if (second == null) { // 1-item var currentType = GetRequestType(newRequest); var previousType = GetRequestType(first); if (currentType == RequestType.Ui || (currentType == previousType)) { // - Ui requests discard everything // - non-Ui request discard non-Ui request first = newRequest; } else { // we got here if current type is non-Ui and prev type is Ui second = newRequest; } } else { // 2 items // the only situation with we can have 2 requests in queue is [Ui; Non-Ui] Debug.Assert(GetRequestType(first) == RequestType.Ui); Debug.Assert(GetRequestType(second) == RequestType.NonUi); var requestType = GetRequestType(newRequest); if (requestType == RequestType.Ui) { // discard both old requests first = newRequest; second = null; } else { // replace non-Ui request with a new one second = newRequest; } } } } /// <summary> /// Checks if request queue contains request similar to the given one. /// </summary> public bool ContainsSimilarRequest(BackgroundRequest_DEPRECATED request) { var requestType = GetRequestType(request); lock (syncRoot) { if (first == null) { // 0 items return false; } else if (second == null) { // 1 item return requestType == GetRequestType(first); } else { // 2 items (both Ui and non-Ui) return true; } } } /// <summary> /// Gets request from queue /// </summary> /// <returns></returns> public BackgroundRequest_DEPRECATED Dequeue() { lock (syncRoot) { Debug.Assert(first != null); var tmp = first; first = second; second = null; return tmp; } } /// <summary> /// Discards all requests added so far and enqueues specified request. /// </summary> public void Set(BackgroundRequest_DEPRECATED r) { lock (syncRoot) { first = null; second = null; Enqueue(r); } } /// for tests only!!! internal int Count { get { if (first == null) return 0; if (second == null) return 1; return 2; } } private static RequestType GetRequestType(BackgroundRequest_DEPRECATED r) { switch (r.Reason) { case BackgroundRequestReason.ParseFile: case BackgroundRequestReason.FullTypeCheck: return RequestType.NonUi; default: return RequestType.Ui; } } } internal abstract class IntellisenseInfo_DEPRECATED { internal abstract System.Tuple<string,TextSpan> GetDataTipText(int line, int col); internal abstract Microsoft.FSharp.Control.FSharpAsync<Declarations_DEPRECATED> GetDeclarations(ITextSnapshot textSnapshot, int line, int col, BackgroundRequestReason reason); internal abstract Microsoft.FSharp.Core.FSharpOption<MethodListForAMethodTip_DEPRECATED> GetMethodListForAMethodTip(); internal abstract GotoDefinitionResult_DEPRECATED Goto(IVsTextView textView, int line, int col); internal abstract void GetF1KeywordString(TextSpan span, IVsUserContext context); } // Note, this class is only implemented once in the F# Language Service implementation, in the F# code which implements the // declaration set. It would be better if all the implementation details in this code were put in the F# code. internal abstract class Declarations_DEPRECATED { internal abstract bool IsEmpty(); internal abstract int GetCount(string filterText); internal abstract string GetDisplayText(string filterText, int index); internal abstract String GetName(string filterText, int index); internal abstract string GetNameInCode(string filterText, int index); internal abstract String GetDescription(string filterText, int index); internal abstract int GetGlyph(string filterText, int index); internal abstract BackgroundRequestReason Reason { get; } // return whether this is a uniqueMatch or not internal abstract void GetBestMatch(string filterText, String value, out int index, out bool uniqueMatch, out bool shouldSelectItem); internal abstract bool IsCommitChar(char commitCharacter); internal abstract string OnCommit(string filterText, int index); // This method allows the implementer to do something after completion is finished, for example, // in the XML editor the when the user selects a start tag name "<foo", this method is used to // insert the end tag automatically "></foo>". The framework makes sure this method is called at // the right time, after VS has actually inserted the result from OnCommit, in this case "foo". // It returns one more character to process, which may itself be a trigger for more intellisense. internal abstract char OnAutoComplete(IVsTextView textView, string committedText, char commitCharacter, int index); } //------------------------------------------------------------------------------------- // Note: DEPRECATED CODE ONLY ACTIVE IN UNIT TESTING VIA "UNROSLYNIZED" UNIT TESTS. // // Note: Tests using this code should either be adjusted to test the corresponding feature in // FSharp.Editor, or deleted. However, the tests may be exercising underlying F# Compiler // functionality and thus have considerable value, they should ony be deleted if we are sure this // is not the case. // // represents all the information necessary to display and navigate withing a method tip (e.g. param info, overloads, ability to move thru overloads and params) internal abstract class MethodListForAMethodTip_DEPRECATED { internal abstract string GetName(int index); internal abstract int GetCount(); internal abstract string GetDescription(int index); internal abstract string GetReturnTypeText(int index); internal abstract int GetParameterCount(int index); internal abstract void GetParameterInfo(int index, int parameter, out string name, out string display, out string description); internal abstract int GetColumnOfStartOfLongId(); // 0-based - this is used for left aligning the tip that gets drawn on the screen internal abstract bool IsThereACloseParen(); // false if either this is a call without parens "f x" or the parser recovered as in "f(x,y" internal abstract Tuple<int, int>[] GetNoteworthyParamInfoLocations(); // 0-based: longId start, longId end, open paren, <tuple ends> (see below) - relative to the ITextSnapshot this was created against // let resultVal = some.functionOrMethod.call ( arg1 , arg2 ) // ^ ^ ^ ^ ^ // start of call identifier ^ ^ ^ ^ ^ // end of call identifier ^ ^ ^ ^ // open paren (or start of first arg if no paren) ^ ^ ^ // end of ^ ^ // each arg ^ // // and thus arg ranges are e.g. computed to be: |--------|-------| // and so when in those regions, we bold that param internal abstract ITrackingSpan[] GetParameterRanges(); // GetNoteworthyParamInfoLocations above is for unit testing; VS uses GetParameterRanges instead, to track changes as user types // TODO can we remove GetNoteworthyParamInfoLocations and move unit tests to GetParameterRanges? internal abstract string[] GetParameterNames(); // an entry for each actual parameter, either null, or the parameter name if this is a named parameter (e.g. "f(0,y=4)" has [|null;"y"|] ) internal virtual string OpenBracket { get { return "("; } } internal virtual string CloseBracket { get { return ")"; } } internal virtual string Delimiter { get { return ", "; } } internal virtual bool TypePrefixed { get { return false; } } internal virtual string TypePrefix { get { return null; } } internal virtual string TypePostfix { get { return null; } } } internal class BraceMatch_DEPRECATED { internal TextSpan a; internal TextSpan b; internal int priority; internal BraceMatch_DEPRECATED(TextSpan a, TextSpan b, int priority) { this.a = a; this.b = b; this.priority = priority; } } internal class TripleMatch : BraceMatch_DEPRECATED { internal TextSpan c; internal TripleMatch(TextSpan a, TextSpan b, TextSpan c, int priority) : base(a, b, priority) { this.c = c; } } internal delegate void OnErrorAddedHandler(string path, string subcategory, string message, TextSpan context, Severity sev); /// <summary> /// AuthoringSink is used to gather information from the parser to help in the following: /// - error reporting /// - matching braces (ctrl-]) /// - intellisense: Member Selection, CompleteWord, QuickInfo, MethodTips /// - management of the autos window in the debugger /// - breakpoint validation /// </summary> /// internal class AuthoringSink { internal BackgroundRequestReason reason; internal int line; internal int col; internal ArrayList Spans; internal ArrayList Braces; internal bool foundMatchingBrace; internal ArrayList errors; private int[] errorCounts; private int maxErrors; public event OnErrorAddedHandler OnErrorAdded; internal AuthoringSink(BackgroundRequestReason reason, int line, int col, int maxErrors) { this.reason = reason; this.errors = new ArrayList(); this.line = line; this.col = col; this.Spans = new ArrayList(); this.Braces = new ArrayList(); this.errorCounts = new int[4]; this.maxErrors = maxErrors; } internal int Line { get { return this.line; } } internal int Column { get { return this.col; } } internal BackgroundRequestReason Reason { get { return this.reason; } } internal bool FoundMatchingBrace { get { return this.foundMatchingBrace; } set { this.foundMatchingBrace = value; } } private void AddBraces(BraceMatch_DEPRECATED b) { this.foundMatchingBrace = true; int i = 0; for (int n = this.Braces.Count; i < n; i++) { BraceMatch_DEPRECATED a = (BraceMatch_DEPRECATED)this.Braces[i]; if (a.priority < b.priority) break; } this.Braces.Insert(i, b); } /// <summary>Use this property to find if your parser should call MatchPair or MatchTriple</summary> internal bool BraceMatching { get { switch (this.reason) { case BackgroundRequestReason.MatchBraces: case BackgroundRequestReason.MatchBracesAndMethodTip: case BackgroundRequestReason.MemberSelectAndHighlightBraces: return true; } return false; } } /// <summary> /// Whenever a matching pair is parsed, e.g. '{' and '}', this method is called /// with the text span of both the left and right item. The /// information is used when a user types "ctrl-]" in VS /// to find a matching brace and when auto-highlight matching /// braces is enabled. A priority can also be given so that multiple overlapping pairs /// can be prioritized for brace matching. The matching pair with the highest priority /// (largest integer value) wins. /// </summary> internal virtual void MatchPair(TextSpan span, TextSpan endContext, int priority) { if (BraceMatching) { TextSpanHelper.MakePositive(ref span); TextSpanHelper.MakePositive(ref endContext); if (TextSpanHelper.ContainsInclusive(span, this.line, this.col) || TextSpanHelper.ContainsInclusive(endContext, this.line, this.col)) { this.Spans.Add(span); this.Spans.Add(endContext); AddBraces(new BraceMatch_DEPRECATED(span, endContext, priority)); } } } /// <summary> /// Matching tripples are used to highlight in bold a completed statement. For example /// when you type the closing brace on a foreach statement VS highlights in bold the statement /// that was closed. The first two source contexts are the beginning and ending of the statement that /// opens the block (for example, the span of the "foreach(...){" and the third source context /// is the closing brace for the block (e.g., the "}"). A priority can also be given so that /// multiple overlapping pairs can be prioritized for brace matching. /// The matching pair with the highest priority (largest integer value) wins. /// </summary> internal virtual void MatchTriple(TextSpan startSpan, TextSpan middleSpan, TextSpan endSpan, int priority) { if (BraceMatching) { TextSpanHelper.MakePositive(ref startSpan); TextSpanHelper.MakePositive(ref middleSpan); TextSpanHelper.MakePositive(ref endSpan); if (TextSpanHelper.ContainsInclusive(startSpan, this.line, this.col) || TextSpanHelper.ContainsInclusive(middleSpan, this.line, this.col) || TextSpanHelper.ContainsInclusive(endSpan, this.line, this.col)) { this.Spans.Add(startSpan); this.Spans.Add(middleSpan); this.Spans.Add(endSpan); AddBraces(new TripleMatch(startSpan, middleSpan, endSpan, priority)); } } } /// <summary> /// AutoExpression is in support of IVsLanguageDebugInfo.GetProximityExpressions. /// It is called for each expression that might be interesting for /// a user in the "Auto Debugging" window. All names that are /// set using StartName and QualifyName are already automatically /// added to the "Auto" window! This means that AutoExpression /// is rarely used. /// </summary> internal virtual void AutoExpression(TextSpan expr) { } /// <summary> /// CodeSpan is in support of IVsLanguageDebugInfo.ValidateBreakpointLocation. /// It is called for each region that contains "executable" code. /// This is used to validate breakpoints. Comments are /// automatically taken care of based on TokenInfo returned from scanner. /// Normally this method is called when a procedure is started/ended. /// </summary> internal virtual void CodeSpan(TextSpan span) { } /// <summary> /// Add an error message. This method also filters out duplicates so you only /// see the unique errors in the error list window. /// </summary> internal virtual void AddError(string path, string subcategory, string message, TextSpan context, Severity sev) { if (context.iStartLine < 0 || context.iEndLine < context.iStartLine || context.iStartIndex < 0 || (context.iEndLine == context.iStartLine && context.iEndIndex < context.iStartIndex)) { //TODO: reenable this! //Debug.Assert(false); return; } int i = (int)sev; if (this.errorCounts[i] == this.maxErrors) return; // reached maximum // Make sure the error is unique. foreach (ErrorNode n in this.errors) { if ((TextSpanHelper.IsSameSpan(n.context, context) || TextSpanHelper.IsEmbedded(n.context, context) || TextSpanHelper.IsEmbedded(context, n.context)) && n.message == message && n.severity == sev && n.uri == path) { return; // then it's a duplicate! } } this.errorCounts[i]++; this.errors.Add(new ErrorNode(path, subcategory, message, context, sev)); if (this.OnErrorAdded != null) this.OnErrorAdded.Invoke(path, subcategory, message, context, sev); } }; // AuthoringSink internal class ErrorNode { internal string uri; internal string message; internal string subcategory; internal TextSpan context; internal Severity severity; internal ErrorNode(string uri, string subcategory, string message, TextSpan context, Severity severity) { this.uri = uri; this.subcategory = subcategory; this.message = message; this.context = context; this.severity = severity; } } }