/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.ui.editors.sql.syntax;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.jkiss.dbeaver.ui.editors.sql.SQLEditor;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* SQLReconcilingStrategy
*/
public class SQLReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension
{
private SQLEditor editor;
private IDocument document;
/**
* The offset of the next character to be read
*/
protected int regionOffset;
/**
* The end offset of the range to be scanned
*/
protected int regionLength;
/**
* @return Returns the editor.
*/
public SQLEditor getEditor()
{
return editor;
}
public void setEditor(SQLEditor editor)
{
this.editor = editor;
}
@Override
public void setDocument(IDocument document)
{
this.document = document;
}
@Override
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion)
{
regionOffset = dirtyRegion.getOffset();
regionLength = dirtyRegion.getLength();
calculatePositions();
}
@Override
public void reconcile(IRegion partition)
{
regionOffset = partition.getOffset();
regionLength = partition.getLength();
calculatePositions();
}
@Override
public void setProgressMonitor(IProgressMonitor monitor)
{
}
@Override
public void initialReconcile()
{
regionOffset = 0;
regionLength = document.getLength();
calculatePositions();
}
protected void calculatePositions()
{
ProjectionAnnotationModel annotationModel = editor.getAnnotationModel();
if (annotationModel == null) {
return;
}
Set<SQLScriptPosition> removedPositions = editor.getRuleManager().getRemovedPositions(true);
Set<SQLScriptPosition> addedPositions = editor.getRuleManager().getAddedPositions(true);
Annotation[] removedAnnotations = null;
if (!removedPositions.isEmpty()) {
removedAnnotations = new Annotation[removedPositions.size()];
int index = 0;
for (SQLScriptPosition pos : removedPositions) {
removedAnnotations[index++] = pos.getFoldingAnnotation();
}
}
Map<Annotation, Position> addedAnnotations = null;
if (!addedPositions.isEmpty()) {
addedAnnotations = new HashMap<>();
for (SQLScriptPosition pos : addedPositions) {
addedAnnotations.put(pos.getFoldingAnnotation(), pos);
}
}
if (removedAnnotations != null || addedAnnotations != null) {
annotationModel.modifyAnnotations(
removedAnnotations,
addedAnnotations,
null);
}
/*
final List<Position> positions;
try {
positions = parseRegion();
} catch (BadLocationException e) {
e.printStackTrace();
return;
}
*/
/*
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
editor.updateFoldingStructure(regionOffset, regionLength, positions);
}
});
*/
}
/**
* P
* @throws BadLocationException
*
private List<Position> parseRegion()
throws BadLocationException
{
List<Position> positions = new ArrayList<Position>();
int endPosition = regionOffset + regionLength;
int statementStartPos = 0;
for (int pos = regionOffset; pos < endPosition; pos++) {
char ch = document.getChar(pos);
if (ch == STATEMENT_DIV) {
positions.add(new Position(statementStartPos, pos - statementStartPos));
statementStartPos = pos + 1;
}
}
// Add trailing position
if (statementStartPos < endPosition) {
positions.add(new Position(statementStartPos, endPosition));
}
return positions;
}*/
/**
* emits tokens to {@link #positions}.
*
* @return number of newLines
* @throws BadLocationException
*
protected int recursiveTokens(int depth)
throws BadLocationException
{
int newLines = 0;
while (cNextPos < regionLength) {
while (cNextPos < regionLength) {
char ch = document.getChar(cNextPos++);
switch (ch) {
case '<':
int startOffset = cNextPos - 1;
int startNewLines = newLines;
int classification = classifyTag();
String tagString = document.get(startOffset,
Math.min(cNextPos - startOffset,
regionLength - startOffset)); // this is to see where we are in the debugger
newLines += cNewLines; // cNewLines is written by
// classifyTag()
switch (classification) {
case START_TAG:
newLines += recursiveTokens(depth + 1);
if (newLines > startNewLines + 1) {
emitPosition(startOffset, cNextPos - startOffset);
}
break;
case LEAF_TAG:
if (newLines > startNewLines + 1) {
emitPosition(startOffset, cNextPos - startOffset);
}
break;
case COMMENT_TAG:
if (newLines > startNewLines + 1) {
emitPosition(startOffset, cNextPos - startOffset);
}
break;
case PI_TAG:
break;
case END_TAG:
case EOR_TAG:
return newLines;
default:
break;
}
break;
case '\n':
case '\r':
if ((ch == cLastNLChar) || (' ' == cLastNLChar)) {
newLines++;
cLastNLChar = ch;
}
break;
default:
break;
}
}
}
return newLines;
}
protected void emitPosition(int startOffset, int length)
{
positions.add(new Position(startOffset, length));
}*/
/**
* classsifies a tag: <br />
* <?...?>: {@link #PI_TAG} <br />
* <!...-->: {@link #COMMENT_TAG} <br />
* <...>: {@link #START_TAG} <br />
* <.../>: {@link #LEAF_TAG} <br />
* </...>: {@link #END_TAG} <br />
* <...: {@link #EOR_TAG} (end of range reached before closing > is
* found). <br />
* when this method is called, {@link #cNextPos} must point to the character
* after <, when it returns, it points to the character after > or
* after the range. About syntax errors: this method is not a validator, it
* is useful. Side effect: writes number of found newLines to
* {@link #cNewLines}.
*
* @return the tag classification
*
protected int classifyTag()
{
try {
char ch = document.getChar(cNextPos++);
cNewLines = 0;
// processing instruction?
if ('?' == ch) {
boolean piFlag = false;
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if (('>' == ch) && piFlag)
return PI_TAG;
piFlag = ('?' == ch);
}
return EOR_TAG;
}
// comment?
if ('!' == ch) {
cNextPos++; // must be '-' but we don't care if not
cNextPos++; // must be '-' but we don't care if not
int commEnd = 0;
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if (('>' == ch) && (commEnd >= 2))
return COMMENT_TAG;
if (('\n' == ch) || ('\r' == ch)) {
if ((ch == cLastNLChar) || (' ' == cLastNLChar)) {
cNewLines++;
cLastNLChar = ch;
}
}
if ('-' == ch) {
commEnd++;
} else {
commEnd = 0;
}
}
return EOR_TAG;
}
// consume whitespaces
while ((' ' == ch) || ('\t' == ch) || ('\n' == ch) || ('\r' == ch)) {
ch = document.getChar(cNextPos++);
if (cNextPos > regionLength)
return EOR_TAG;
}
// end tag?
if ('/' == ch) {
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('>' == ch) {
cNewLines += eatToEndOfLine();
return END_TAG;
}
if ('"' == ch) {
ch = document.getChar(cNextPos++);
while ((cNextPos < regionLength) && ('"' != ch)) {
ch = document.getChar(cNextPos++);
}
} else if ('\'' == ch) {
ch = document.getChar(cNextPos++);
while ((cNextPos < regionLength) && ('\'' != ch)) {
ch = document.getChar(cNextPos++);
}
}
}
return EOR_TAG;
}
// start tag or leaf tag?
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
// end tag?
s:
switch (ch) {
case '/':
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('>' == ch) {
cNewLines += eatToEndOfLine();
return LEAF_TAG;
}
}
return EOR_TAG;
case '"':
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('"' == ch)
break s;
}
return EOR_TAG;
case '\'':
while (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('\'' == ch)
break s;
}
return EOR_TAG;
case '>':
cNewLines += eatToEndOfLine();
return START_TAG;
default:
break;
}
}
return EOR_TAG;
} catch (BadLocationException e) {
// should not happen, but we treat it as end of range
return EOR_TAG;
}
}
protected int eatToEndOfLine()
throws BadLocationException
{
if (cNextPos >= regionLength) {
return 0;
}
char ch = document.getChar(cNextPos++);
// 1. eat all spaces and tabs
while ((cNextPos < regionLength) && ((' ' == ch) || ('\t' == ch))) {
ch = document.getChar(cNextPos++);
}
if (cNextPos >= regionLength) {
cNextPos--;
return 0;
}
// now ch is a new line or a non-whitespace
if ('\n' == ch) {
if (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('\r' != ch) {
cNextPos--;
}
} else {
cNextPos--;
}
return 1;
}
if ('\r' == ch) {
if (cNextPos < regionLength) {
ch = document.getChar(cNextPos++);
if ('\n' != ch) {
cNextPos--;
}
} else {
cNextPos--;
}
return 1;
}
return 0;
}*/
}