Django Editor in VS2010 – more speed needed

Last week I asked Alex to give the editor a 'real try' and try to break it. Guess what - he did it. All he did was he opened a template of a reasonable size and started typing. He types fast and as he typed the editor slowed to a crawl.

And understandably so. Here is what's happening. Every keystroke is a change to the buffer with the template source code. It triggers the Change event on the buffer, which in turn initiates re-parsing of the template using updated template source. Yes, parsing is happening in a separate thread, and a single parsing request is something the parser can easily handle in the background without noticeable effect on the UI, but for bigger templates it can take some time for parsing to complete and with the typing speed of 10-15 keystrokes per second the requests start to pile up.

Thankfully there is no need to do it anew every time a key is hit - all we need is to parse the last one. Essentially when I type fast I do not really care for the parsing results, but when I pause in my typing I would like to see how good is what I've done so far.

I already discussed the parsing speed issue in the Part 2 Background Parsing of the editor series. I listed there some things which can be done to address the speed issue. I implemented them all from the get go, except for the last one - the parsing requests queuing. And when Alex broke my beautiful editor, I realized that I am not getting away without implementing the queuing too.

Here is what I need: every time a parsing request comes in I schedule its execution for .5 sec in the future. If during the delay another request comes in, instead of creating a new request, I reschedule the active request so it is still set to run .5 sec after the last buffer modification. Once this delay is implemented parser will not run until there is a .5 sec pause in buffer modifications. In other words all requests with less then .5 sec time between them will be combined into one.

And here is the code to do this:

        public NodeProvider(IVsOutputWindowPane djangoDiagnostics, IParser parser, ITextBuffer buffer)
        {
            this.djangoDiagnostics = djangoDiagnostics;
            this.parser = parser;
            this.buffer = buffer;
            filePath = ((ITextDocument)buffer.Properties[typeof(ITextDocument)]).FilePath;
            buffer.Changed += new EventHandler(buffer_Changed);
            // we need to run rebuildNodes on a separate thread. Using timer
            // for this seems to be an overkill, but we need the timer anyway so - why not
            parserTimer =
                new Timer(rebuildNodes, buffer.CurrentSnapshot, 0, Timeout.Infinite);
        }
 
        /// Initiates the delayed parsing in response to the buffer changed event
        private void buffer_Changed(object sender, TextContentChangedEventArgs e)
        {
            // shut down the old one
            parserTimer.Dispose();
 
            // put the call to the rebuildNodes on timer
            parserTimer =  new Timer(rebuildNodes,  e.After,  PARSING_DELAY, Timeout.Infinite);
        }

Leave a Reply