blob: 3c6f436f3f037ca87e2fb88574db8d9520ea1aef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 xored software, Inc. and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Sergey Kanshin)
*******************************************************************************/
package org.eclipse.dltk.tcl.formatter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.dltk.compiler.SourceElementRequestorAdaptor;
import org.eclipse.dltk.formatter.AbstractScriptFormatter;
import org.eclipse.dltk.formatter.FormatterContext;
import org.eclipse.dltk.formatter.FormatterDocument;
import org.eclipse.dltk.tcl.ast.TclCommand;
import org.eclipse.dltk.tcl.formatter.internal.DumpContentException;
import org.eclipse.dltk.tcl.formatter.internal.FormatterIndentDetector;
import org.eclipse.dltk.tcl.formatter.internal.FormatterWorker;
import org.eclipse.dltk.tcl.formatter.internal.Messages;
import org.eclipse.dltk.tcl.formatter.internal.TclFormatterPlugin;
import org.eclipse.dltk.tcl.formatter.internal.TclFormatterWriter;
import org.eclipse.dltk.tcl.formatter.internal.UnexpectedFormatterException;
import org.eclipse.dltk.tcl.internal.structure.TclSourceElementParser2;
import org.eclipse.dltk.ui.formatter.FormatterException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
@SuppressWarnings("restriction")
public class TclFormatter extends AbstractScriptFormatter {
protected static final String[] INDENTING = { TclFormatterConstants.INDENT_SCRIPT,
TclFormatterConstants.INDENT_AFTER_BACKSLASH };
protected static final String[] BLANK_LINES = { TclFormatterConstants.LINES_FILE_AFTER_PACKAGE,
TclFormatterConstants.LINES_FILE_BETWEEN_PROC };
private final String lineDelimiter;
public TclFormatter(String lineDelimiter, Map<String, ? extends Object> preferences) {
super(preferences);
this.lineDelimiter = lineDelimiter;
}
@Override
public int detectIndentationLevel(IDocument document, int offset) {
if (offset == 0) {
return 0;
}
final String input = document.get();
List<TclCommand> commands = parse(input);
final FormatterIndentDetector detector = new FormatterIndentDetector(offset);
return detector.getLevel(commands);
}
@Override
public TextEdit format(String source, int offset, int length, int indent) throws FormatterException {
final String input = source.substring(offset, offset + length);
List<TclCommand> commands = parse(input);
final String output = format(input, commands, indent);
if (output != null) {
if (!input.equals(output)) {
if (!isValidation() || equalsIgnoreBlanks(new StringReader(input), new StringReader(output))) {
return new ReplaceEdit(offset, length, output);
} else {
TclFormatterPlugin.log(new Status(IStatus.ERROR, TclFormatterPlugin.PLUGIN_ID, IStatus.OK,
Messages.TclFormatter_contentCorrupted, new DumpContentException(input)));
}
} else {
return new MultiTextEdit(); // NOP
}
}
return null;
}
protected List<TclCommand> parse(final String input) {
TclSourceElementParser2 parser = new TclSourceElementParser2();
parser.setRequestor(new SourceElementRequestorAdaptor());
return parser.parse(input, 0);
}
private String format(String input, List<TclCommand> commands, int indent) throws FormatterException {
FormatterDocument document = createDocument(input);
final TclFormatterWriter writer = new TclFormatterWriter(document, lineDelimiter, createIndentGenerator());
writer.setWrapLength(getInt(TclFormatterConstants.WRAP_COMMENTS_LENGTH));
writer.setLinesPreserve(getInt(TclFormatterConstants.LINES_PRESERVE));
// tclWriter.setTrimEmptyLines(false);
// tclWriter.setTrimTrailingSpaces(false);
try {
final FormatterContext context = new FormatterContext(indent);
final FormatterWorker worker = new FormatterWorker(writer, document, context);
worker.format(commands);
writer.flush(context);
return writer.getOutput();
} catch (UnexpectedFormatterException e) {
throw new FormatterException(e);
}
}
private FormatterDocument createDocument(String input) {
FormatterDocument document = new FormatterDocument(input);
for (int i = 0; i < INDENTING.length; ++i) {
document.setBoolean(INDENTING[i], getBoolean(INDENTING[i]));
}
for (int i = 0; i < BLANK_LINES.length; ++i) {
document.setInt(BLANK_LINES[i], getInt(BLANK_LINES[i]));
}
document.setInt(TclFormatterConstants.FORMATTER_TAB_SIZE, getInt(TclFormatterConstants.FORMATTER_TAB_SIZE));
document.setBoolean(TclFormatterConstants.WRAP_COMMENTS, getBoolean(TclFormatterConstants.WRAP_COMMENTS));
return document;
}
protected boolean isValidation() {
return !getBoolean(TclFormatterConstants.WRAP_COMMENTS);
}
private boolean equalsIgnoreBlanks(Reader inputReader, Reader outputReader) {
LineNumberReader input = new LineNumberReader(inputReader);
LineNumberReader output = new LineNumberReader(outputReader);
for (;;) {
final String inputLine = readLine(input);
final String outputLine = readLine(output);
if (inputLine == null) {
if (outputLine == null) {
return true;
} else {
return false;
}
} else if (outputLine == null) {
return false;
} else if (!inputLine.equals(outputLine)) {
return false;
}
}
}
private String readLine(LineNumberReader reader) {
String line;
do {
try {
line = reader.readLine();
} catch (IOException e) {
// should not happen
return null;
}
if (line == null) {
return line;
}
line = line.trim();
} while (line.length() == 0);
return line;
}
}