/******************************************************************************* * 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; } }