// 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 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 lastActiveView = 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>[] GetParameterLocations(); // 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();  // GetParameterLocations above is for unit testing; VS uses GetParameterRanges instead, to track changes as user types // TODO can we remove GetParameterLocations 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 triples 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;
        }
    }


}
