// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.debug.jsdtbridge; import org.chromium.debug.core.model.JavaScriptFormatter; import org.chromium.debug.core.model.StringMappingData; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.jsdt.core.JavaScriptCore; import org.eclipse.wst.jsdt.core.ToolFactory; import org.eclipse.wst.jsdt.core.formatter.CodeFormatter; /** * JSDT-based implementation of {@link JavaScriptFormatter}. */ public class JsdtFormatterBridge implements JavaScriptFormatter { public Result format(String sourceString) { TextEdit textEdit = jsdtFormat(sourceString); if (textEdit == null) { final boolean useFallbackFormatter = true; if (useFallbackFormatter) { // While JSDT formatter has chances to fail // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=329716), // there is a fall-back implementation, that only insert new-lines in some places // thus making a source a bit more readable. String header = Messages.JsdtFormatterBridge_FALLBACK_COMMENT; textEdit = AdHocFormatter.format(sourceString, header); } else { throw new RuntimeException("Formatter failed"); //$NON-NLS-1$ } } return convertResult(sourceString, textEdit); } private TextEdit jsdtFormat(String sourceString) { CodeFormatter jsdtFormatter = ToolFactory.createCodeFormatter( JavaScriptCore.getDefaultOptions()); TextEdit textEdit = jsdtFormatter.format(CodeFormatter.K_JAVASCRIPT_UNIT, sourceString, 0, sourceString.length(), 0, LINE_END_STRING); return textEdit; } private Result convertResult(String sourceString, TextEdit textEdit) { IntBuffer intBuffer = new IntBuffer(); final Position origPos = new Position(0, 0); final Position dstPos = new Position(0, 0); TextEdit[] editList = textEdit.getChildren(); int sourceStringPos = 0; int editListPos = 0; int nextLineEndPos = sourceString.indexOf(LINE_END_CHAR); final StringBuilder builder = new StringBuilder(); // Iterate over all edits and all untouched line ends. while (true) { ReplaceEdit replaceEdit; int nextEditPos = -1; { // Find next applicable edit. This is a potential cycle if we skip some changes. if (editListPos < editList.length) { if (editList[editListPos] instanceof ReplaceEdit == false) { throw new RuntimeException(); } replaceEdit = (ReplaceEdit) editList[editListPos]; nextEditPos = replaceEdit.getOffset(); } else { replaceEdit = null; } } // Choose what comes first: line end or edit. boolean processLineEndNotEdit; if (nextEditPos == -1) { if (nextLineEndPos == -1) { break; } else { processLineEndNotEdit = true; } } else { if (nextLineEndPos == -1) { processLineEndNotEdit = false; } else { processLineEndNotEdit = nextLineEndPos < nextEditPos; } } if (processLineEndNotEdit) { // Process next line end. builder.append(sourceString.substring(sourceStringPos, nextLineEndPos + 1)); origPos.line++; origPos.col = 0; dstPos.line++; dstPos.col = 0; sourceStringPos = nextLineEndPos + 1; nextLineEndPos = sourceString.indexOf(LINE_END_CHAR, sourceStringPos); } else { // Process next edit. builder.append(sourceString.substring(sourceStringPos, nextEditPos)); origPos.col += nextEditPos - sourceStringPos; dstPos.col += nextEditPos - sourceStringPos; origPos.writeToArray(intBuffer); dstPos.writeToArray(intBuffer); // Count removed line ends. if (replaceEdit.getLength() > 0) { String removedString = sourceString.substring(replaceEdit.getOffset(), replaceEdit.getOffset() + replaceEdit.getLength()); origPos.advanceToString(removedString); } // Count added line ends. builder.append(replaceEdit.getText()); dstPos.advanceToString(replaceEdit.getText()); origPos.writeToArray(intBuffer); dstPos.writeToArray(intBuffer); sourceStringPos = nextEditPos + replaceEdit.getLength(); editListPos++; if (nextLineEndPos != -1 && nextLineEndPos < sourceStringPos) { nextLineEndPos = sourceString.indexOf(LINE_END_CHAR, sourceStringPos); } } } builder.append(sourceString.substring(sourceStringPos)); origPos.col += sourceString.length() - sourceStringPos; dstPos.col += sourceString.length() - sourceStringPos; final int[] inputArray = new int[intBuffer.size() / 2]; final int[] formattedArray = new int[intBuffer.size() / 2]; for (int i = 0; i < inputArray.length; i += 2) { inputArray[i] = intBuffer.get(i * 2 + 0); inputArray[i + 1] = intBuffer.get(i * 2 + 1); formattedArray[i] = intBuffer.get(i * 2 + 2); formattedArray[i + 1] = intBuffer.get(i * 2 + 3); } final StringMappingData inputTextData = new StringMappingData(inputArray, origPos.line, origPos.col); final StringMappingData formattedTextData = new StringMappingData(formattedArray, dstPos.line, dstPos.col); return new Result() { public String getFormattedText() { return builder.toString(); } public StringMappingData getInputTextData() { return inputTextData; } public StringMappingData getFormattedTextData() { return formattedTextData; } }; } private static class Position { Position(int line, int col) { this.line = line; this.col = col; } int line; int col; void advanceToString(String str) { int innerPos = 0; while (true) { int innerLineEndPos = str.indexOf('\n', innerPos); if (innerLineEndPos == -1) { break; } line++; col = 0; innerPos = innerLineEndPos + 1; } col += str.length() - innerPos; } void writeToArray(IntBuffer buffer) { buffer.add2(line, col); } } // Exponentially-growing buffer for ints. private static class IntBuffer { private int[] array = new int[INITIAL_SIZE]; private int pos = 0; public void add2(int i1, int i2) { if (pos + 2 > array.length) { int[] newArray = new int[array.length * 2]; System.arraycopy(array, 0, newArray, 0, pos); array = newArray; } array[pos] = i1; array[pos + 1] = i2; pos += 2; } public int get(int pos) { return array[pos]; } public int size() { return pos; } private static int INITIAL_SIZE = 10; } private static final char LINE_END_CHAR = '\n'; private static final String LINE_END_STRING = LINE_END_CHAR + ""; //$NON-NLS-1$ }