private void doWork(DBRProgressMonitor monitor) throws BadLocationException {
        TextViewer viewer = this.editor.getTextViewer();
        if (viewer == null || this.editor.getRuleManager() == null) {
            this.signalAccomplished();
            return;
        }
        Interval actualFragment = UIUtils.syncExec(new RunnableWithResult<>() {
            public Interval runWithResult() {
                if (viewer.getDocument() == null) {
                    return null;
                }
                IDocument doc = viewer.getDocument();

                int stepsToKeep = 2;
                int startOffset = viewer.getTopIndexStartOffset();

                int firstVisibleLine = 0;
                try {
                    firstVisibleLine = doc.getLineOfOffset(startOffset);
                    int visibleLinesCount = viewer.getTextWidget().getSize().y / viewer.getTextWidget().getLineHeight();
                    int rangeStart = doc.getLineOffset(Math.max(0, firstVisibleLine - visibleLinesCount * stepsToKeep));
                    int rangeEnd = doc.getLineOffset(Math.min(doc.getNumberOfLines(), firstVisibleLine + visibleLinesCount * (stepsToKeep + 1)));
                    return new Interval(rangeStart, rangeEnd);
                } catch (BadLocationException e) {
                    int endOffset = viewer.getBottomIndexEndOffset();
                    return new Interval(startOffset, endOffset);
                }
            }
        });

        if (actualFragment == null) {
            this.signalAccomplished();
            return;
        }
        int workOffset;
        int workLength;
        try {
            synchronized (this.syncRoot) {
                this.isRunning = true;
                
                // drop unnecessary items
                if (DEBUG) {
                    log.debug("actual region is " + actualFragment.a + "-" + actualFragment.b);
                }
                Interval preservedRegion = this.context.dropInvisibleScriptItems(actualFragment);
                this.knownRegionStart = preservedRegion.a;
                this.knownRegionEnd = preservedRegion.b; 
                if (DEBUG) {
                    log.debug("preserved is " + knownRegionStart + "-" + knownRegionEnd);
                    log.debug("queued ranges total: " + this.queuedForReparse.size());
                }
                
                // TODO reparse only changed elements
                // for now just cover the region of interest
                {
                    NodesIterator<QueuedRegionInfo> it = this.queuedForReparse.nodesIteratorAt(0);
                    workOffset = (it.getCurrValue() != null || it.next()) ? it.getCurrOffset() : 0;
                }
                {
                    NodesIterator<QueuedRegionInfo> it = this.queuedForReparse.nodesIteratorAt(Integer.MAX_VALUE);
                    workLength = (it.getCurrValue() != null || it.prev())
                        ? (it.getCurrOffset() + it.getCurrValue().length - workOffset) : 0;
                }
                
                // truncate work region to fit within actualFragment,
                // as we've dropped what is outside already, so not point to parse outside of it
                Interval workInterval = new Interval(workOffset, saturatedSum(workOffset, workLength));
                if (!actualFragment.properlyContains(workInterval)) {
                    workInterval = actualFragment.intersection(workInterval);
                    workOffset = workInterval.a;
                    workLength = workInterval.length();
                }

                int docTailDelta = this.document.getLength() - saturatedSum(workOffset, workLength);
                if (docTailDelta < 0) {
                    workLength += docTailDelta;
                }
                if (DEBUG) {
                    {
                        NodesIterator<QueuedRegionInfo> it = this.queuedForReparse.nodesIteratorAt(Integer.MAX_VALUE);
                        while (it.prev()) {
                            log.debug("\t@" + it.getCurrOffset() + "+" + it.getCurrValue().length);
                        }
                    }
                }
                
                this.queuedForReparse.clear();
                if (DEBUG) {
                    log.debug("doWork: queuedForReparse count is " + queuedForReparse.size());
                }
            }
        } catch (Throwable ex) {
            log.error(ex);
            this.signalAccomplished();
            return;
        }

        try {
            if (workLength == 0) {
                this.signalAccomplished();
                return;
            }

            SQLParserContext parserContext = new SQLParserContext(
                editor.getDataSource(), editor.getSyntaxManager(), editor.getRuleManager(), document
            );
            if (DEBUG) {
                log.debug("discovering " + workOffset + "+" + workLength);
            }
            {
                SQLScriptElement firstElement = SQLScriptParser.extractQueryAtPos(parserContext, workOffset, false);
                if (firstElement != null) {
                    workLength = Math.max(workOffset + workLength, firstElement.getOffset() + firstElement.getLength());
                    workOffset = Math.min(workOffset, firstElement.getOffset());
                    workLength -= workOffset;
                }
            }
            List<SQLScriptElement> elements = SQLScriptParser.extractScriptQueries(
                parserContext, workOffset, workLength, false, false, false
            );
            if (elements.isEmpty()) {
                if (DEBUG) {
                    log.debug("No script elements to parse in range " + workOffset + "+" + workLength);
                }
                this.accomplishWork(workOffset, workLength);
                return;
            } else {
                SQLScriptElement element = SQLScriptParser.extractQueryAtPos(parserContext, elements.get(0).getOffset(), false);
                if (element != null && element.getOffset() < elements.get(0).getOffset()) {
                    elements.set(0, element);
                }
                int lastElementIndex = elements.size() - 1;
                SQLScriptElement lastElement = elements.get(lastElementIndex);
                if (elements.size() > 1) {
                    element = SQLScriptParser.extractQueryAtPos(parserContext, lastElement.getOffset(), false);
                    if (element != null) {
                        elements.set(lastElementIndex, element);
                        lastElement = element;
                    }
                }
                {
                    SQLScriptElement followingElement = SQLScriptParser.extractNextQuery(parserContext, lastElement, true);
                    if (followingElement != null &&
                        followingElement.getOffset() < workOffset + workLength &&
                        followingElement.getOffset() > lastElement.getOffset() + lastElement.getLength()) {
                        elements.add(followingElement);
                    }
                }
            }
            
            {
                SQLScriptElement lastElement = elements.get(elements.size() - 1);
                if (lastElement == null) {
                    this.accomplishWork(workOffset, workLength);
                    return;
                }
                workOffset = elements.get(0).getOffset();
                workLength = lastElement.getOffset() + lastElement.getLength() - workOffset;
                if (DEBUG) {
                    log.debug("firstElement@" + elements.get(0).getOffset() + ":" + elements.get(0).getText());
                    log.debug("lastElement@" + elements.get(elements.size() - 1).getOffset() + ":" + elements.get(elements.size() - 1).getText());
                    log.debug("parsing " + workOffset + "+" + workLength);
                }
            }
            if (DEBUG) {
                log.debug("{");
                for (var e : elements) {
                    log.debug("    @" + e.getOffset() + "+" + e.getLength() + " " + (e instanceof SQLQuery q && q.isEndsWithDelimiter()));
                }
                log.debug("}");
            }

            boolean useRealMetadata = this.editor.isReadMetadataForQueryAnalysisEnabled();
            DBCExecutionContext executionContext = this.editor.getExecutionContext();

            monitor.beginTask("Background query analysis for " + editor.getTitle(), 1 + elements.size());
            monitor.worked(1);

            SQLSyntaxManager syntaxManager = this.editor.getSyntaxManager();
            SQLDialect dialect = this.obtainCurrentSqlDialect(executionContext);

            SQLQueryRecognitionContext recognitionContext = new SQLQueryRecognitionContext(monitor, executionContext, useRealMetadata, syntaxManager, dialect);

            int i = 1;
            for (SQLScriptElement element : elements) {
                if (monitor.isCanceled()) {
                    break;
                }
                try {
                    recognitionContext.reset();
                    SQLQueryModel queryModel = element instanceof SQLControlCommand
                        ? SQLCommandModelRecognizer.recognizeCommand(
                            recognitionContext,
                            element.getText(),
                            this.editor instanceof SQLEditor e ? e.getGlobalScriptContext() : null
                        )
                        : SQLQueryModelRecognizer.recognizeQuery(recognitionContext, element.getOriginalText());

                    if (queryModel != null) {
                        if (DEBUG) {
                            log.debug("registering script item @" + element.getOffset() + "+" + element.getLength());
                        }
                        SQLDocumentScriptItemSyntaxContext itemContext = this.context.registerScriptItemContext(
                            element.getOriginalText(),
                            queryModel,
                            element.getOffset(),
                            element.getLength(),
                            element instanceof SQLQuery queryElement && Boolean.TRUE.equals(queryElement.isEndsWithDelimiter())
                        );
                        itemContext.clear();
                        List<SQLQueryRecognitionProblemInfo> problems = recognitionContext.getProblems();
                        if (problems.size() >= SQLQueryRecognitionProblemInfo.PER_QUERY_LIMIT && queryModel.getQueryModel() != null) {
                            problems.add(new SQLQueryRecognitionProblemInfo(
                                SQLQueryRecognitionProblemInfo.Severity.WARNING,
                                queryModel.getSyntaxNode(),
                                null,
                                "Too many errors found in one query of " + this.editor.getTitle() + "!" +
                                    " Displaying first " + SQLQueryRecognitionProblemInfo.PER_QUERY_LIMIT + " of them.",
                                null
                            ));
                        }
                        itemContext.setProblems(problems);
                        for (SQLQuerySymbolEntry entry : queryModel.getAllSymbols()) {
                            itemContext.registerToken(entry.getInterval().a, entry);
                        }
                        itemContext.refreshCompleted();
                    }
                } catch (Throwable ex) {
                    log.debug("Error while analyzing query text: " + element.getOriginalText(), ex);
                }
                monitor.worked(1);
                monitor.subTask("Background query analysis: subtask #" + i + " of " + elements.size());
                i++;
            }
            this.context.resetLastAccessCache();
        } catch (Throwable ex) {
            log.debug(ex);
        } finally {
            monitor.done();
        }

        int parsedOffset = workOffset;
        int parsedLength = workLength;

        this.accomplishWork(parsedOffset, parsedLength);

        UIUtils.asyncExec(() -> {
            viewer.invalidateTextPresentation(parsedOffset, parsedLength);
        });
    }
Prepare completion context for the specified position in the text
plugins/org.jkiss.dbeaver.ui.editors.sql/src/org/jkiss/dbeaver/ui/editors/sql/semantics/SQLBackgroundParsingJob.java