// 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 java.util.HashMap; import java.util.Map; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; /** * A relatively simple {@link String#indexOf}-based formatter that inserts newlines * after semicolons and maintains indentations based on curly brace symbols it meets. The quality * of work is quite poor: it doesn't recognize string literals or comments and will format their * internals as well. * <p> * (Technical details: in JavaScript one cannot detect RegExp syntax without doing a full parsing; * since RegExp syntax allows quotes among other symbols, one cannot also detect string literals; * consequently there's no way one can detect comments. That's why all this syntax elements remain * unrecognized.) */ public class AdHocFormatter { public static TextEdit format(String source, String header) { FormatSession session = new FormatSession(source, header); session.run(); return session.getResult(); } private static class FormatSession { private int position = 0; private final String header; private final String source; private final SpaceCache spaceCache = new SpaceCache(); private final MultiTextEdit result = new MultiTextEdit(); private LastSeenState currentState = LastSeenState.NEW_LINE; private enum LastSeenState { OPEN_BRACE, CLOSE_BRACE, SEMICOLON, NEW_LINE, NON_SPACE } private int currentNesting = 0; FormatSession(String source, String header) { this.source = source; this.header = header; } void run() { result.addChild(new ReplaceEdit(0, 0, header)); while (position < source.length()) { { char ch = source.charAt(position); switch (ch) { case ';': handleSemicolon(); break; case '{': handleOpenBrace(); break; case '}': handleCloseBrace(); break; case '\r': case '\n': handleLineEnd(); break; case ' ': case '\t': // Ignore. break; default: handleNonSpace(); } position++; } } } TextEdit getResult() { return result; } private void handleLineEnd() { currentState = LastSeenState.NEW_LINE; } private void handleSemicolon() { currentState = LastSeenState.SEMICOLON; } private void handleOpenBrace() { if (currentState == LastSeenState.SEMICOLON || currentState == LastSeenState.CLOSE_BRACE || currentState == LastSeenState.OPEN_BRACE) { insertNewLine(); } currentNesting++; currentState = LastSeenState.OPEN_BRACE; } private void handleCloseBrace() { if (currentNesting > 0) { currentNesting--; } if (currentState != LastSeenState.NEW_LINE) { insertNewLine(); } currentState = LastSeenState.CLOSE_BRACE; } private void handleNonSpace() { if (currentState == LastSeenState.SEMICOLON || currentState == LastSeenState.CLOSE_BRACE || currentState == LastSeenState.OPEN_BRACE) { insertNewLine(); } currentState = LastSeenState.NON_SPACE; } private void insertNewLine() { result.addChild(new ReplaceEdit(position, 0, spaceCache.getSpace(currentNesting * 2))); } // Caches instances of strings that starts with a new-line and contains of n spaces. private static class SpaceCache { private final Map<Integer, String> map = new HashMap<Integer, String>(); public String getSpace(int len) { String result = map.get(len); if (result == null) { StringBuilder builder = new StringBuilder(len + 1); builder.append('\n'); for (int i = 0; i < len; i++) { builder.append(' '); } result = builder.toString(); map.put(len, result); } return result; } } } }