forked from qt-creator/qt-creator
		
	QmlJS: Introduce a new indenter that works similarly to the new C++ one.
Done-with: Thomas Hartmann
This commit is contained in:
		| @@ -56,6 +56,11 @@ OTHER_FILES += \ | ||||
|     $$PWD/parser/qmljs.g | ||||
|  | ||||
| contains(QT, gui) { | ||||
|     SOURCES += $$PWD/qmljsindenter.cpp | ||||
|     HEADERS += $$PWD/qmljsindenter.h | ||||
|     SOURCES += \ | ||||
|         $$PWD/qmljsindenter.cpp \ | ||||
|         $$PWD/qmljscodeformatter.cpp | ||||
|  | ||||
|     HEADERS += \ | ||||
|         $$PWD/qmljsindenter.h \ | ||||
|         $$PWD/qmljscodeformatter.h | ||||
| } | ||||
|   | ||||
							
								
								
									
										910
									
								
								src/libs/qmljs/qmljscodeformatter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										910
									
								
								src/libs/qmljs/qmljscodeformatter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,910 @@ | ||||
| /************************************************************************** | ||||
| ** | ||||
| ** This file is part of Qt Creator | ||||
| ** | ||||
| ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). | ||||
| ** | ||||
| ** Contact: Nokia Corporation (qt-info@nokia.com) | ||||
| ** | ||||
| ** Commercial Usage | ||||
| ** | ||||
| ** Licensees holding valid Qt Commercial licenses may use this file in | ||||
| ** accordance with the Qt Commercial License Agreement provided with the | ||||
| ** Software or, alternatively, in accordance with the terms contained in | ||||
| ** a written agreement between you and Nokia. | ||||
| ** | ||||
| ** GNU Lesser General Public License Usage | ||||
| ** | ||||
| ** Alternatively, this file may be used under the terms of the GNU Lesser | ||||
| ** General Public License version 2.1 as published by the Free Software | ||||
| ** Foundation and appearing in the file LICENSE.LGPL included in the | ||||
| ** packaging of this file.  Please review the following information to | ||||
| ** ensure the GNU Lesser General Public License version 2.1 requirements | ||||
| ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | ||||
| ** | ||||
| ** If you are unsure which license is appropriate for your use, please | ||||
| ** contact the sales department at http://qt.nokia.com/contact. | ||||
| ** | ||||
| **************************************************************************/ | ||||
|  | ||||
| #include "qmljscodeformatter.h" | ||||
|  | ||||
| #include <QtCore/QDebug> | ||||
| #include <QtCore/QMetaEnum> | ||||
| #include <QtGui/QTextDocument> | ||||
| #include <QtGui/QTextCursor> | ||||
| #include <QtGui/QTextBlock> | ||||
|  | ||||
| using namespace QmlJS; | ||||
|  | ||||
| CodeFormatter::BlockData::BlockData() | ||||
|     : m_blockRevision(-1) | ||||
| { | ||||
| } | ||||
|  | ||||
| CodeFormatter::CodeFormatter() | ||||
|     : m_indentDepth(0) | ||||
|     , m_tabSize(4) | ||||
| { | ||||
| } | ||||
|  | ||||
| CodeFormatter::~CodeFormatter() | ||||
| { | ||||
| } | ||||
|  | ||||
| void CodeFormatter::setTabSize(int tabSize) | ||||
| { | ||||
|     m_tabSize = tabSize; | ||||
| } | ||||
|  | ||||
| void CodeFormatter::recalculateStateAfter(const QTextBlock &block) | ||||
| { | ||||
|     restoreCurrentState(block.previous()); | ||||
|  | ||||
|     const int lexerState = tokenizeBlock(block); | ||||
|     m_tokenIndex = 0; | ||||
|     m_newStates.clear(); | ||||
|  | ||||
|     //qDebug() << "Starting to look at " << block.text() << block.blockNumber() + 1; | ||||
|  | ||||
|     for (; m_tokenIndex < m_tokens.size(); ) { | ||||
|         m_currentToken = tokenAt(m_tokenIndex); | ||||
|         const int kind = extendedTokenKind(m_currentToken); | ||||
|         //dump(); | ||||
|         //qDebug() << "Token" << m_currentLine.mid(m_currentToken.begin(), m_currentToken.length) << m_tokenIndex << "in line" << block.blockNumber() + 1; | ||||
|  | ||||
|         if (kind == Comment | ||||
|                 && state().type != multiline_comment_cont | ||||
|                 && state().type != multiline_comment_start) { | ||||
|             m_tokenIndex += 1; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         switch (m_currentState.top().type) { | ||||
|         case topmost_intro: | ||||
|             switch (kind) { | ||||
|             case Identifier:    enter(objectdefinition_or_js); continue; | ||||
|             case Import:        enter(top_qml); continue; | ||||
|             default:            enter(top_js); continue; | ||||
|             } break; | ||||
|  | ||||
|         case top_qml: | ||||
|             switch (kind) { | ||||
|             case Import:        enter(import_start); break; | ||||
|             case Identifier:    enter(binding_or_objectdefinition); break; | ||||
|             } break; | ||||
|  | ||||
|         case top_js: | ||||
|             tryStatement(); | ||||
|             break; | ||||
|  | ||||
|         case objectdefinition_or_js: | ||||
|             switch (kind) { | ||||
|             case Dot:           break; | ||||
|             case Identifier: | ||||
|                 if (!m_currentLine.at(m_currentToken.begin()).isUpper()) { | ||||
|                     turnInto(top_js); | ||||
|                     continue; | ||||
|                 } | ||||
|                 break; | ||||
|             case LeftBrace:     turnInto(binding_or_objectdefinition); continue; | ||||
|             default:            turnInto(top_js); continue; | ||||
|             } break; | ||||
|  | ||||
|         case import_start: | ||||
|             enter(import_maybe_dot_or_version_or_as); | ||||
|             break; | ||||
|  | ||||
|         case import_maybe_dot_or_version_or_as: | ||||
|             switch (kind) { | ||||
|             case Dot:           turnInto(import_dot); break; | ||||
|             case As:            turnInto(import_as); break; | ||||
|             case Number:        turnInto(import_maybe_as); break; | ||||
|             default:            leave(); leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case import_maybe_as: | ||||
|             switch (kind) { | ||||
|             case As:            turnInto(import_as); break; | ||||
|             default:            leave(); leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case import_dot: | ||||
|             switch (kind) { | ||||
|             case Identifier:    turnInto(import_maybe_dot_or_version_or_as); break; | ||||
|             default:            leave(); leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case import_as: | ||||
|             switch (kind) { | ||||
|             case Identifier:    leave(); leave(); break; | ||||
|             } break; | ||||
|  | ||||
|         case binding_or_objectdefinition: | ||||
|             switch (kind) { | ||||
|             case Colon:         enter(binding_assignment); break; | ||||
|             case LeftBrace:     enter(objectdefinition_open); break; | ||||
|             } break; | ||||
|  | ||||
|         case binding_assignment: | ||||
|             switch (kind) { | ||||
|             case Semicolon:     leave(true); break; | ||||
|             case If:            enter(if_statement); break; | ||||
|             case LeftBrace:     enter(jsblock_open); break; | ||||
|             case On: | ||||
|             case As: | ||||
|             case List: | ||||
|             case Import: | ||||
|             case Signal: | ||||
|             case Property: | ||||
|             case Identifier:    enter(expression_or_objectdefinition); break; | ||||
|             default:            enter(expression); continue; | ||||
|             } break; | ||||
|  | ||||
|         case objectdefinition_open: | ||||
|             switch (kind) { | ||||
|             case RightBrace:    leave(true); break; | ||||
|             case Default:       enter(default_property_start); break; | ||||
|             case Property:      enter(property_start); break; | ||||
|             case Function:      enter(function_start); break; | ||||
|             case Signal:        enter(signal_start); break; | ||||
|             case On: | ||||
|             case As: | ||||
|             case List: | ||||
|             case Import: | ||||
|             case Identifier:    enter(binding_or_objectdefinition); break; | ||||
|             } break; | ||||
|  | ||||
|         case default_property_start: | ||||
|             if (kind != Property) | ||||
|                 leave(true); | ||||
|             else | ||||
|                 turnInto(property_start); | ||||
|             break; | ||||
|  | ||||
|         case property_start: | ||||
|             switch (kind) { | ||||
|             case Colon:         enter(binding_assignment); break; // oops, was a binding | ||||
|             case Var: | ||||
|             case Identifier:    enter(property_type); break; | ||||
|             case List:          enter(property_list_open); break; | ||||
|             default:            leave(true); continue; | ||||
|             } break; | ||||
|  | ||||
|         case property_type: | ||||
|             turnInto(property_maybe_initializer); | ||||
|             break; | ||||
|  | ||||
|         case property_list_open: | ||||
|             if (m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length) == QLatin1String(">")) | ||||
|                 turnInto(property_maybe_initializer); | ||||
|             break; | ||||
|  | ||||
|         case property_maybe_initializer: | ||||
|             switch (kind) { | ||||
|             case Colon:         enter(binding_assignment); break; | ||||
|             default:            leave(true); continue; | ||||
|             } break; | ||||
|  | ||||
|         case signal_start: | ||||
|             switch (kind) { | ||||
|             case Colon:         enter(binding_assignment); break; // oops, was a binding | ||||
|             default:            enter(signal_maybe_arglist); break; | ||||
|             } break; | ||||
|  | ||||
|         case signal_maybe_arglist: | ||||
|             switch (kind) { | ||||
|             case LeftParenthesis:   turnInto(signal_arglist_open); break; | ||||
|             default:                leave(true); continue; | ||||
|             } break; | ||||
|  | ||||
|         case signal_arglist_open: | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|         case function_start: | ||||
|             switch (kind) { | ||||
|             case LeftParenthesis:   enter(function_arglist_open); break; | ||||
|             } break; | ||||
|  | ||||
|         case function_arglist_open: | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  turnInto(function_arglist_closed); break; | ||||
|             } break; | ||||
|  | ||||
|         case function_arglist_closed: | ||||
|             switch (kind) { | ||||
|             case LeftBrace:         turnInto(jsblock_open); break; | ||||
|             default:                leave(true); continue; // error recovery | ||||
|             } break; | ||||
|  | ||||
|         case expression_or_objectdefinition: | ||||
|             switch (kind) { | ||||
|             case LeftBrace:     turnInto(objectdefinition_open); break; | ||||
|             default:            enter(expression); continue; // really? first token already gone! | ||||
|             } break; | ||||
|  | ||||
|         case expression: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case Comma: | ||||
|             case Delimiter:         enter(expression_continuation); break; | ||||
|             case RightBracket: | ||||
|             case RightParenthesis:  leave(); continue; | ||||
|             case RightBrace:        leave(true); continue; | ||||
|             case Semicolon:         leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|         case expression_continuation: | ||||
|             leave(); | ||||
|             continue; | ||||
|  | ||||
|         case expression_maybe_continuation: | ||||
|             switch (kind) { | ||||
|             case Question: | ||||
|             case Delimiter: | ||||
|             case LeftBracket: | ||||
|             case LeftParenthesis:  leave(); continue; | ||||
|             default:               leave(true); continue; | ||||
|             } break; | ||||
|  | ||||
|         case paren_open: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  leave(); break; | ||||
|             } break; | ||||
|  | ||||
|         case bracket_open: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case Comma:             enter(bracket_element_start); break; | ||||
|             case RightBracket:      leave(); break; | ||||
|             } break; | ||||
|  | ||||
|         case bracket_element_start: | ||||
|             switch (kind) { | ||||
|             case Identifier:        turnInto(bracket_element_maybe_objectdefinition); break; | ||||
|             default:                leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case bracket_element_maybe_objectdefinition: | ||||
|             switch (kind) { | ||||
|             case LeftBrace:         turnInto(objectdefinition_open); break; | ||||
|             default:                leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case ternary_op: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightParenthesis: | ||||
|             case RightBracket: | ||||
|             case RightBrace: | ||||
|             case Comma: | ||||
|             case Semicolon:         leave(); continue; | ||||
|             case Colon:             enter(expression); break; // entering expression makes maybe_continuation work | ||||
|             } break; | ||||
|  | ||||
|         case jsblock_open: | ||||
|         case substatement_open: | ||||
|             if (tryStatement()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightBrace:        leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|         case substatement: | ||||
|             // prefer substatement_open over block_open | ||||
|             if (kind != LeftBrace) { | ||||
|                 if (tryStatement()) | ||||
|                     break; | ||||
|             } | ||||
|             switch (kind) { | ||||
|             case LeftBrace:         turnInto(substatement_open); break; | ||||
|             } break; | ||||
|  | ||||
|         case if_statement: | ||||
|             switch (kind) { | ||||
|             case LeftParenthesis:   enter(condition_open); break; | ||||
|             default:                leave(true); break; // error recovery | ||||
|             } break; | ||||
|  | ||||
|         case maybe_else: | ||||
|             if (kind == Else) { | ||||
|                 turnInto(else_clause); | ||||
|                 enter(substatement); | ||||
|                 break; | ||||
|             } else { | ||||
|                 leave(true); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|         case else_clause: | ||||
|             // ### shouldn't happen | ||||
|             dump(); | ||||
|             Q_ASSERT(false); | ||||
|             leave(true); | ||||
|             break; | ||||
|  | ||||
|         case condition_open: | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  turnInto(substatement); break; | ||||
|             case LeftParenthesis:   enter(condition_paren_open); break; | ||||
|             } break; | ||||
|  | ||||
|         // paren nesting | ||||
|         case condition_paren_open: | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  leave(); break; | ||||
|             case LeftParenthesis:   enter(condition_paren_open); break; | ||||
|             } break; | ||||
|  | ||||
|         case switch_statement: | ||||
|         case statement_with_condition: | ||||
|             switch (kind) { | ||||
|             case LeftParenthesis:   enter(statement_with_condition_paren_open); break; | ||||
|             default:                leave(true); | ||||
|             } break; | ||||
|  | ||||
|         case statement_with_condition_paren_open: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  turnInto(substatement); break; | ||||
|             } break; | ||||
|  | ||||
|         case statement_with_block: | ||||
|             switch (kind) { | ||||
|             case LeftBrace:         enter(jsblock_open); break; | ||||
|             default:                leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|         case do_statement: | ||||
|             switch (kind) { | ||||
|             case While:             break; | ||||
|             case LeftParenthesis:   enter(do_statement_while_paren_open); break; | ||||
|             default:                leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|         case do_statement_while_paren_open: | ||||
|             if (tryInsideExpression()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightParenthesis:  leave(); leave(true); break; | ||||
|             } break; | ||||
|  | ||||
|             break; | ||||
|  | ||||
|         case case_start: | ||||
|             switch (kind) { | ||||
|             case Colon:             turnInto(case_cont); break; | ||||
|             } break; | ||||
|  | ||||
|         case case_cont: | ||||
|             if (kind != Case && kind != Default && tryStatement()) | ||||
|                 break; | ||||
|             switch (kind) { | ||||
|             case RightBrace:        leave(); continue; | ||||
|             case Default: | ||||
|             case Case:              leave(); continue; | ||||
|             } break; | ||||
|  | ||||
|         case multiline_comment_start: | ||||
|         case multiline_comment_cont: | ||||
|             if (kind != Comment) { | ||||
|                 leave(); | ||||
|                 continue; | ||||
|             } else if (m_tokenIndex == m_tokens.size() - 1 | ||||
|                        && lexerState == Scanner::Normal) { | ||||
|                 leave(); | ||||
|             } else if (m_tokenIndex == 0) { | ||||
|                 // to allow enter/leave to update the indentDepth | ||||
|                 turnInto(multiline_comment_cont); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             qWarning() << "Unhandled state" << m_currentState.top().type; | ||||
|             break; | ||||
|         } // end of state switch | ||||
|  | ||||
|         ++m_tokenIndex; | ||||
|     } | ||||
|  | ||||
|     int topState = m_currentState.top().type; | ||||
|  | ||||
|     if (topState == expression | ||||
|             || topState == expression_or_objectdefinition) { | ||||
|         enter(expression_maybe_continuation); | ||||
|     } | ||||
|     if (topState != multiline_comment_start | ||||
|             && topState != multiline_comment_cont | ||||
|             && lexerState == Scanner::MultiLineComment) { | ||||
|         enter(multiline_comment_start); | ||||
|     } | ||||
|  | ||||
|     saveCurrentState(block); | ||||
| } | ||||
|  | ||||
| int CodeFormatter::indentFor(const QTextBlock &block) | ||||
| { | ||||
| //    qDebug() << "indenting for" << block.blockNumber() + 1; | ||||
|  | ||||
|     restoreCurrentState(block.previous()); | ||||
|     correctIndentation(block); | ||||
|     return m_indentDepth; | ||||
| } | ||||
|  | ||||
| void CodeFormatter::updateStateUntil(const QTextBlock &endBlock) | ||||
| { | ||||
|     QStack<State> previousState = initialState(); | ||||
|     QTextBlock it = endBlock.document()->firstBlock(); | ||||
|  | ||||
|     // find the first block that needs recalculation | ||||
|     for (; it.isValid() && it != endBlock; it = it.next()) { | ||||
|         BlockData blockData; | ||||
|         if (!loadBlockData(it, &blockData)) | ||||
|             break; | ||||
|         if (blockData.m_blockRevision != it.revision()) | ||||
|             break; | ||||
|         if (previousState != blockData.m_beginState) | ||||
|             break; | ||||
|         if (loadLexerState(it) == -1) | ||||
|             break; | ||||
|  | ||||
|         previousState = blockData.m_endState; | ||||
|     } | ||||
|  | ||||
|     if (it == endBlock) | ||||
|         return; | ||||
|  | ||||
|     // update everthing until endBlock | ||||
|     for (; it.isValid() && it != endBlock; it = it.next()) { | ||||
|         recalculateStateAfter(it); | ||||
|     } | ||||
|  | ||||
|     // invalidate everything below by marking the state in endBlock as invalid | ||||
|     if (it.isValid()) { | ||||
|         BlockData invalidBlockData; | ||||
|         saveBlockData(&it, invalidBlockData); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CodeFormatter::updateLineStateChange(const QTextBlock &block) | ||||
| { | ||||
|     if (!block.isValid()) | ||||
|         return; | ||||
|  | ||||
|     BlockData blockData; | ||||
|     if (loadBlockData(block, &blockData) && blockData.m_blockRevision == block.revision()) | ||||
|         return; | ||||
|  | ||||
|     recalculateStateAfter(block); | ||||
|  | ||||
|     // invalidate everything below by marking the next block's state as invalid | ||||
|     QTextBlock next = block.next(); | ||||
|     if (!next.isValid()) | ||||
|         return; | ||||
|  | ||||
|     saveBlockData(&next, BlockData()); | ||||
| } | ||||
|  | ||||
| CodeFormatter::State CodeFormatter::state(int belowTop) const | ||||
| { | ||||
|     if (belowTop < m_currentState.size()) | ||||
|         return m_currentState.at(m_currentState.size() - 1 - belowTop); | ||||
|     else | ||||
|         return State(); | ||||
| } | ||||
|  | ||||
| const QVector<CodeFormatter::State> &CodeFormatter::newStatesThisLine() const | ||||
| { | ||||
|     return m_newStates; | ||||
| } | ||||
|  | ||||
| int CodeFormatter::tokenIndex() const | ||||
| { | ||||
|     return m_tokenIndex; | ||||
| } | ||||
|  | ||||
| int CodeFormatter::tokenCount() const | ||||
| { | ||||
|     return m_tokens.size(); | ||||
| } | ||||
|  | ||||
| const Token &CodeFormatter::currentToken() const | ||||
| { | ||||
|     return m_currentToken; | ||||
| } | ||||
|  | ||||
| void CodeFormatter::invalidateCache(QTextDocument *document) | ||||
| { | ||||
|     if (!document) | ||||
|         return; | ||||
|  | ||||
|     BlockData invalidBlockData; | ||||
|     QTextBlock it = document->firstBlock(); | ||||
|     for (; it.isValid(); it = it.next()) { | ||||
|         saveBlockData(&it, invalidBlockData); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CodeFormatter::enter(int newState) | ||||
| { | ||||
|     int savedIndentDepth = m_indentDepth; | ||||
|     onEnter(newState, &m_indentDepth, &savedIndentDepth); | ||||
|     State s(newState, savedIndentDepth); | ||||
|     m_currentState.push(s); | ||||
|     m_newStates.push(s); | ||||
|  | ||||
|     if (newState == bracket_open) | ||||
|         enter(bracket_element_start); | ||||
| } | ||||
|  | ||||
| void CodeFormatter::leave(bool statementDone) | ||||
| { | ||||
|     Q_ASSERT(m_currentState.size() > 1); | ||||
|     if (m_currentState.top().type == topmost_intro) | ||||
|         return; | ||||
|  | ||||
|     if (m_newStates.size() > 0) | ||||
|         m_newStates.pop(); | ||||
|  | ||||
|     // restore indent depth | ||||
|     State poppedState = m_currentState.pop(); | ||||
|     m_indentDepth = poppedState.savedIndentDepth; | ||||
|  | ||||
|     int topState = m_currentState.top().type; | ||||
|  | ||||
|     // if statement is done, may need to leave recursively | ||||
|     if (statementDone) { | ||||
|         if (!isExpressionEndState(topState)) | ||||
|             leave(true); | ||||
|         if (topState == if_statement) { | ||||
|             if (poppedState.type != maybe_else) | ||||
|                 enter(maybe_else); | ||||
|             else | ||||
|                 leave(true); | ||||
|         } else if (topState == else_clause) { | ||||
|             // leave the else *and* the surrounding if, to prevent another else | ||||
|             leave(); | ||||
|             leave(true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CodeFormatter::correctIndentation(const QTextBlock &block) | ||||
| { | ||||
|     const int lexerState = tokenizeBlock(block); | ||||
|     Q_ASSERT(m_currentState.size() >= 1); | ||||
|  | ||||
|     adjustIndent(m_tokens, lexerState, &m_indentDepth); | ||||
| } | ||||
|  | ||||
| bool CodeFormatter::tryInsideExpression(bool alsoExpression) | ||||
| { | ||||
|     int newState = -1; | ||||
|     const int kind = extendedTokenKind(m_currentToken); | ||||
|     switch (kind) { | ||||
|     case LeftParenthesis:   newState = paren_open; break; | ||||
|     case LeftBracket:       newState = bracket_open; break; | ||||
|     case Function:          newState = function_start; break; | ||||
|     case Question:          newState = ternary_op; break; | ||||
|     } | ||||
|  | ||||
|     if (newState != -1) { | ||||
|         if (alsoExpression) | ||||
|             enter(expression); | ||||
|         enter(newState); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool CodeFormatter::tryStatement() | ||||
| { | ||||
|     const int kind = extendedTokenKind(m_currentToken); | ||||
|     switch (kind) { | ||||
|     case Semicolon: | ||||
|         enter(empty_statement); | ||||
|         leave(true); | ||||
|         return true; | ||||
|     case Return: | ||||
|         enter(return_statement); | ||||
|         enter(expression); | ||||
|         return true; | ||||
|     case While: | ||||
|     case For: | ||||
|     case Catch: | ||||
|         enter(statement_with_condition); | ||||
|         return true; | ||||
|     case Switch: | ||||
|         enter(switch_statement); | ||||
|         return true; | ||||
|     case If: | ||||
|         enter(if_statement); | ||||
|         return true; | ||||
|     case Do: | ||||
|         enter(do_statement); | ||||
|         enter(substatement); | ||||
|         return true; | ||||
|     case Case: | ||||
|     case Default: | ||||
|         enter(case_start); | ||||
|         return true; | ||||
|     case Try: | ||||
|     case Finally: | ||||
|         enter(statement_with_block); | ||||
|         return true; | ||||
|     case LeftBrace: | ||||
|         enter(jsblock_open); | ||||
|         return true; | ||||
|     case Identifier: | ||||
|     case Delimiter: | ||||
|     case PlusPlus: | ||||
|     case MinusMinus: | ||||
|     case Import: | ||||
|     case Signal: | ||||
|     case On: | ||||
|     case As: | ||||
|     case List: | ||||
|     case Property: | ||||
|     case Function: | ||||
|         enter(expression); | ||||
|         // look at the token again | ||||
|         m_tokenIndex -= 1; | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool CodeFormatter::isBracelessState(int type) const | ||||
| { | ||||
|     return | ||||
|             type == if_statement || | ||||
|             type == else_clause || | ||||
|             type == substatement || | ||||
|             type == binding_assignment || | ||||
|             type == binding_or_objectdefinition; | ||||
| } | ||||
|  | ||||
| bool CodeFormatter::isExpressionEndState(int type) const | ||||
| { | ||||
|     return | ||||
|             type == topmost_intro || | ||||
|             type == top_js || | ||||
|             type == objectdefinition_open || | ||||
|             type == if_statement || | ||||
|             type == else_clause || | ||||
|             type == do_statement || | ||||
|             type == jsblock_open || | ||||
|             type == substatement_open || | ||||
|             type == bracket_open || | ||||
|             type == paren_open || | ||||
|             type == case_cont; | ||||
| } | ||||
|  | ||||
| const Token &CodeFormatter::tokenAt(int idx) const | ||||
| { | ||||
|     static const Token empty; | ||||
|     if (idx < 0 || idx >= m_tokens.size()) | ||||
|         return empty; | ||||
|     else | ||||
|         return m_tokens.at(idx); | ||||
| } | ||||
|  | ||||
| int CodeFormatter::column(int index) const | ||||
| { | ||||
|     int col = 0; | ||||
|     if (index > m_currentLine.length()) | ||||
|         index = m_currentLine.length(); | ||||
|  | ||||
|     const QChar tab = QLatin1Char('\t'); | ||||
|  | ||||
|     for (int i = 0; i < index; i++) { | ||||
|         if (m_currentLine[i] == tab) { | ||||
|             col = ((col / m_tabSize) + 1) * m_tabSize; | ||||
|         } else { | ||||
|             col++; | ||||
|         } | ||||
|     } | ||||
|     return col; | ||||
| } | ||||
|  | ||||
| QStringRef CodeFormatter::currentTokenText() const | ||||
| { | ||||
|     return m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length); | ||||
| } | ||||
|  | ||||
| void CodeFormatter::turnInto(int newState) | ||||
| { | ||||
|     leave(false); | ||||
|     enter(newState); | ||||
| } | ||||
|  | ||||
| void CodeFormatter::saveCurrentState(const QTextBlock &block) | ||||
| { | ||||
|     if (!block.isValid()) | ||||
|         return; | ||||
|  | ||||
|     BlockData blockData; | ||||
|     blockData.m_blockRevision = block.revision(); | ||||
|     blockData.m_beginState = m_beginState; | ||||
|     blockData.m_endState = m_currentState; | ||||
|     blockData.m_indentDepth = m_indentDepth; | ||||
|  | ||||
|     QTextBlock saveableBlock(block); | ||||
|     saveBlockData(&saveableBlock, blockData); | ||||
| } | ||||
|  | ||||
| void CodeFormatter::restoreCurrentState(const QTextBlock &block) | ||||
| { | ||||
|     if (block.isValid()) { | ||||
|         BlockData blockData; | ||||
|         if (loadBlockData(block, &blockData)) { | ||||
|             m_indentDepth = blockData.m_indentDepth; | ||||
|             m_currentState = blockData.m_endState; | ||||
|             m_beginState = m_currentState; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     m_currentState = initialState(); | ||||
|     m_beginState = m_currentState; | ||||
|     m_indentDepth = 0; | ||||
| } | ||||
|  | ||||
| QStack<CodeFormatter::State> CodeFormatter::initialState() | ||||
| { | ||||
|     static QStack<CodeFormatter::State> initialState; | ||||
|     if (initialState.isEmpty()) | ||||
|         initialState.push(State(topmost_intro, 0)); | ||||
|     return initialState; | ||||
| } | ||||
|  | ||||
| int CodeFormatter::tokenizeBlock(const QTextBlock &block) | ||||
| { | ||||
|     int startState = loadLexerState(block.previous()); | ||||
|     if (block.blockNumber() == 0) | ||||
|         startState = 0; | ||||
|     Q_ASSERT(startState != -1); | ||||
|  | ||||
|     Scanner tokenize; | ||||
|     tokenize.setScanComments(true); | ||||
|  | ||||
|     m_currentLine = block.text(); | ||||
|     // to determine whether a line was joined, Tokenizer needs a | ||||
|     // newline character at the end | ||||
|     m_currentLine.append(QLatin1Char('\n')); | ||||
|     m_tokens = tokenize(m_currentLine, startState); | ||||
|  | ||||
|     const int lexerState = tokenize.state(); | ||||
|     QTextBlock saveableBlock(block); | ||||
|     saveLexerState(&saveableBlock, lexerState); | ||||
|     return lexerState; | ||||
| } | ||||
|  | ||||
| CodeFormatter::TokenKind CodeFormatter::extendedTokenKind(const QmlJS::Token &token) const | ||||
| { | ||||
|     const int kind = token.kind; | ||||
|     QStringRef text = m_currentLine.midRef(token.begin(), token.length); | ||||
|  | ||||
|     if (kind == Identifier) { | ||||
|         if (text == "as") | ||||
|             return As; | ||||
|         if (text == "import") | ||||
|             return Import; | ||||
|         if (text == "signal") | ||||
|             return Signal; | ||||
|         if (text == "property") | ||||
|             return Property; | ||||
|         if (text == "on") | ||||
|             return On; | ||||
|         if (text == "list") | ||||
|             return On; | ||||
|     } else if (kind == Keyword) { | ||||
|         const QChar char1 = text.at(0); | ||||
|         const QChar char2 = text.at(1); | ||||
|         const QChar char3 = (text.size() > 2 ? text.at(2) : QChar()); | ||||
|         switch (char1.toLatin1()) { | ||||
|         case 'v': | ||||
|             return Var; | ||||
|         case 'i': | ||||
|             if (char2 == 'f') | ||||
|                 return If; | ||||
|             else if (char3 == 's') | ||||
|                 return Instanceof; | ||||
|             else | ||||
|                 return In; | ||||
|         case 'f': | ||||
|             if (char2 == 'o') | ||||
|                 return For; | ||||
|             else if (char2 == 'u') | ||||
|                 return Function; | ||||
|             else | ||||
|                 return Finally; | ||||
|         case 'e': | ||||
|             return Else; | ||||
|         case 'n': | ||||
|             return New; | ||||
|         case 'r': | ||||
|             return Return; | ||||
|         case 's': | ||||
|             return Switch; | ||||
|         case 'w': | ||||
|             if (char2 == 'h') | ||||
|                 return While; | ||||
|             return With; | ||||
|         case 'c': | ||||
|             if (char3 == 's') | ||||
|                 return Case; | ||||
|             if (char3 == 't') | ||||
|                 return Catch; | ||||
|             return Continue; | ||||
|         case 'd': | ||||
|             if (char3 == 'l') | ||||
|                 return Delete; | ||||
|             if (char3 == 'f') | ||||
|                 return Default; | ||||
|             if (char3 == 'b') | ||||
|                 return Debugger; | ||||
|             return Do; | ||||
|         case 't': | ||||
|             if (char3 == 'i') | ||||
|                 return This; | ||||
|             if (char3 == 'y') | ||||
|                 return Try; | ||||
|             if (char3 == 'r') | ||||
|                 return Throw; | ||||
|             return Typeof; | ||||
|         case 'b': | ||||
|             return Break; | ||||
|         } | ||||
|     } else if (kind == Delimiter) { | ||||
|         if (text == "?") | ||||
|             return Question; | ||||
|         else if (text == "++") | ||||
|             return PlusPlus; | ||||
|         else if (text == "--") | ||||
|             return MinusMinus; | ||||
|     } | ||||
|  | ||||
|     return static_cast<TokenKind>(kind); | ||||
| } | ||||
|  | ||||
| void CodeFormatter::dump() const | ||||
| { | ||||
|     QMetaEnum metaEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType")); | ||||
|  | ||||
|     qDebug() << "Current token index" << m_tokenIndex; | ||||
|     qDebug() << "Current state:"; | ||||
|     foreach (State s, m_currentState) { | ||||
|         qDebug() << metaEnum.valueToKey(s.type) << s.savedIndentDepth; | ||||
|     } | ||||
|     qDebug() << "Current indent depth:" << m_indentDepth; | ||||
| } | ||||
							
								
								
									
										309
									
								
								src/libs/qmljs/qmljscodeformatter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								src/libs/qmljs/qmljscodeformatter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,309 @@ | ||||
| /************************************************************************** | ||||
| ** | ||||
| ** This file is part of Qt Creator | ||||
| ** | ||||
| ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). | ||||
| ** | ||||
| ** Contact: Nokia Corporation (qt-info@nokia.com) | ||||
| ** | ||||
| ** Commercial Usage | ||||
| ** | ||||
| ** Licensees holding valid Qt Commercial licenses may use this file in | ||||
| ** accordance with the Qt Commercial License Agreement provided with the | ||||
| ** Software or, alternatively, in accordance with the terms contained in | ||||
| ** a written agreement between you and Nokia. | ||||
| ** | ||||
| ** GNU Lesser General Public License Usage | ||||
| ** | ||||
| ** Alternatively, this file may be used under the terms of the GNU Lesser | ||||
| ** General Public License version 2.1 as published by the Free Software | ||||
| ** Foundation and appearing in the file LICENSE.LGPL included in the | ||||
| ** packaging of this file.  Please review the following information to | ||||
| ** ensure the GNU Lesser General Public License version 2.1 requirements | ||||
| ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | ||||
| ** | ||||
| ** If you are unsure which license is appropriate for your use, please | ||||
| ** contact the sales department at http://qt.nokia.com/contact. | ||||
| ** | ||||
| **************************************************************************/ | ||||
|  | ||||
| #ifndef QMLJSCODEFORMATTER_H | ||||
| #define QMLJSCODEFORMATTER_H | ||||
|  | ||||
| #include "qmljs_global.h" | ||||
|  | ||||
| #include "qmljsscanner.h" | ||||
|  | ||||
| #include <QtCore/QChar> | ||||
| #include <QtCore/QStack> | ||||
| #include <QtCore/QList> | ||||
| #include <QtCore/QVector> | ||||
| #include <QtCore/QPointer> | ||||
|  | ||||
| QT_BEGIN_NAMESPACE | ||||
| class QTextDocument; | ||||
| class QTextBlock; | ||||
| QT_END_NAMESPACE | ||||
|  | ||||
| namespace QmlJS { | ||||
|  | ||||
| class QMLJS_EXPORT CodeFormatter | ||||
| { | ||||
|     Q_GADGET | ||||
| public: | ||||
|     CodeFormatter(); | ||||
|     virtual ~CodeFormatter(); | ||||
|  | ||||
|     // updates all states up until block if necessary | ||||
|     // it is safe to call indentFor on block afterwards | ||||
|     void updateStateUntil(const QTextBlock &block); | ||||
|  | ||||
|     // calculates the state change introduced by changing a single line | ||||
|     void updateLineStateChange(const QTextBlock &block); | ||||
|  | ||||
|     int indentFor(const QTextBlock &block); | ||||
|  | ||||
|     void setTabSize(int tabSize); | ||||
|  | ||||
|     void invalidateCache(QTextDocument *document); | ||||
|  | ||||
| protected: | ||||
|     virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const = 0; | ||||
|     virtual void adjustIndent(const QList<Token> &tokens, int lexerState, int *indentDepth) const = 0; | ||||
|  | ||||
|     class State; | ||||
|     class BlockData | ||||
|     { | ||||
|     public: | ||||
|         BlockData(); | ||||
|  | ||||
|         QStack<State> m_beginState; | ||||
|         QStack<State> m_endState; | ||||
|         int m_indentDepth; | ||||
|         int m_blockRevision; | ||||
|     }; | ||||
|  | ||||
|     virtual void saveBlockData(QTextBlock *block, const BlockData &data) const = 0; | ||||
|     virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const = 0; | ||||
|  | ||||
|     virtual void saveLexerState(QTextBlock *block, int state) const = 0; | ||||
|     virtual int loadLexerState(const QTextBlock &block) const = 0; | ||||
|  | ||||
| public: // must be public to make Q_GADGET introspection work | ||||
|     enum StateType { | ||||
|         invalid = 0, | ||||
|  | ||||
|         topmost_intro, // The first line in a "topmost" definition. | ||||
|  | ||||
|         top_qml, // root state for qml | ||||
|         top_js, // root for js | ||||
|         objectdefinition_or_js, // file starts with identifier | ||||
|  | ||||
|         multiline_comment_start, | ||||
|         multiline_comment_cont, | ||||
|  | ||||
|         import_start, // after 'import' | ||||
|         import_maybe_dot_or_version_or_as, // after string or identifier | ||||
|         import_dot, // after . | ||||
|         import_maybe_as, // after version | ||||
|         import_as, | ||||
|  | ||||
|         property_start, // after 'property' | ||||
|         default_property_start, // after 'default' | ||||
|         property_type, // after first identifier | ||||
|         property_list_open, // after 'list' as a type | ||||
|         property_maybe_initializer, // after | ||||
|  | ||||
|         signal_start, // after 'signal' | ||||
|         signal_maybe_arglist, // after identifier | ||||
|         signal_arglist_open, // after '(' | ||||
|  | ||||
|         function_start, // after 'function' | ||||
|         function_arglist_open, // after '(' starting function argument list | ||||
|         function_arglist_closed, // after ')' in argument list, expecting '{' | ||||
|  | ||||
|         binding_or_objectdefinition, // after an identifier | ||||
|  | ||||
|         binding_assignment, // after : | ||||
|         objectdefinition_open, // after { | ||||
|  | ||||
|         expression, | ||||
|         expression_continuation, // at the end of the line, when the next line definitely is a continuation | ||||
|         expression_maybe_continuation, // at the end of the line, when the next line may be an expression | ||||
|         expression_or_objectdefinition, // after a binding starting with an identifier ("x: foo") | ||||
|  | ||||
|         paren_open, // opening ( in expression | ||||
|         bracket_open, // opening [ in expression | ||||
|  | ||||
|         bracket_element_start, // after starting bracket_open or after ',' in bracket_open | ||||
|         bracket_element_maybe_objectdefinition, // after an identifier in bracket_element_start | ||||
|  | ||||
|         ternary_op, // The ? : operator | ||||
|  | ||||
|         jsblock_open, | ||||
|  | ||||
|         empty_statement, // for a ';', will never linger | ||||
|  | ||||
|         if_statement, // After 'if' | ||||
|         maybe_else, // after the first substatement in an if | ||||
|         else_clause, // The else line of an if-else construct. | ||||
|  | ||||
|         condition_open, // Start of a condition in 'if', 'while', entered after opening paren | ||||
|         condition_paren_open, // After an lparen in a condition | ||||
|  | ||||
|         substatement, // The first line after a conditional or loop construct. | ||||
|         substatement_open, // The brace that opens a substatement block. | ||||
|  | ||||
|         return_statement, // After 'return' | ||||
|  | ||||
|         statement_with_condition, // After the 'for', 'while', 'catch', ... token | ||||
|         statement_with_condition_paren_open, // While inside the (...) | ||||
|  | ||||
|         statement_with_block, // try, finally | ||||
|  | ||||
|         do_statement, // after 'do' | ||||
|         do_statement_while_paren_open, // after '(' in while clause | ||||
|  | ||||
|         switch_statement, // After 'switch' token | ||||
|         case_start, // after a 'case' or 'default' token | ||||
|         case_cont, // after the colon in a case/default | ||||
|     }; | ||||
|     Q_ENUMS(StateType) | ||||
|  | ||||
| protected: | ||||
|     // extends Token::Kind from qmljsscanner.h | ||||
|     // the entries until EndOfExistingTokenKinds must match | ||||
|     enum TokenKind { | ||||
|         EndOfFile, | ||||
|         Keyword, | ||||
|         Identifier, | ||||
|         String, | ||||
|         Comment, | ||||
|         Number, | ||||
|         LeftParenthesis, | ||||
|         RightParenthesis, | ||||
|         LeftBrace, | ||||
|         RightBrace, | ||||
|         LeftBracket, | ||||
|         RightBracket, | ||||
|         Semicolon, | ||||
|         Colon, | ||||
|         Comma, | ||||
|         Dot, | ||||
|         Delimiter, | ||||
|  | ||||
|         EndOfExistingTokenKinds, | ||||
|  | ||||
|         Break, | ||||
|         Case, | ||||
|         Catch, | ||||
|         Continue, | ||||
|         Debugger, | ||||
|         Default, | ||||
|         Delete, | ||||
|         Do, | ||||
|         Else, | ||||
|         Finally, | ||||
|         For, | ||||
|         Function, | ||||
|         If, | ||||
|         In, | ||||
|         Instanceof, | ||||
|         New, | ||||
|         Return, | ||||
|         Switch, | ||||
|         This, | ||||
|         Throw, | ||||
|         Try, | ||||
|         Typeof, | ||||
|         Var, | ||||
|         Void, | ||||
|         While, | ||||
|         With, | ||||
|  | ||||
|         Import, | ||||
|         Signal, | ||||
|         On, | ||||
|         As, | ||||
|         List, | ||||
|         Property, | ||||
|  | ||||
|         Question, | ||||
|         PlusPlus, | ||||
|         MinusMinus, | ||||
|     }; | ||||
|  | ||||
|     TokenKind extendedTokenKind(const QmlJS::Token &token) const; | ||||
|  | ||||
|     struct State { | ||||
|         State() | ||||
|             : savedIndentDepth(0) | ||||
|             , type(0) | ||||
|         {} | ||||
|  | ||||
|         State(quint8 ty, quint16 savedDepth) | ||||
|             : savedIndentDepth(savedDepth) | ||||
|             , type(ty) | ||||
|         {} | ||||
|  | ||||
|         quint16 savedIndentDepth; | ||||
|         quint8 type; | ||||
|  | ||||
|         bool operator==(const State &other) const { | ||||
|             return type == other.type | ||||
|                 && savedIndentDepth == other.savedIndentDepth; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     State state(int belowTop = 0) const; | ||||
|     const QVector<State> &newStatesThisLine() const; | ||||
|     int tokenIndex() const; | ||||
|     int tokenCount() const; | ||||
|     const Token ¤tToken() const; | ||||
|     const Token &tokenAt(int idx) const; | ||||
|     int column(int position) const; | ||||
|  | ||||
|     bool isBracelessState(int type) const; | ||||
|     bool isExpressionEndState(int type) const; | ||||
|  | ||||
|     void dump() const; | ||||
|  | ||||
| private: | ||||
|     void recalculateStateAfter(const QTextBlock &block); | ||||
|     void saveCurrentState(const QTextBlock &block); | ||||
|     void restoreCurrentState(const QTextBlock &block); | ||||
|  | ||||
|     QStringRef currentTokenText() const; | ||||
|  | ||||
|     int tokenizeBlock(const QTextBlock &block); | ||||
|  | ||||
|     void turnInto(int newState); | ||||
|  | ||||
|     bool tryInsideExpression(bool alsoExpression = false); | ||||
|     bool tryStatement(); | ||||
|  | ||||
|     void enter(int newState); | ||||
|     void leave(bool statementDone = false); | ||||
|     void correctIndentation(const QTextBlock &block); | ||||
|  | ||||
| private: | ||||
|     static QStack<State> initialState(); | ||||
|  | ||||
|     QStack<State> m_beginState; | ||||
|     QStack<State> m_currentState; | ||||
|     QStack<State> m_newStates; | ||||
|  | ||||
|     QList<Token> m_tokens; | ||||
|     QString m_currentLine; | ||||
|     Token m_currentToken; | ||||
|     int m_tokenIndex; | ||||
|  | ||||
|     // should store indent level and padding instead | ||||
|     int m_indentDepth; | ||||
|  | ||||
|     int m_tabSize; | ||||
| }; | ||||
|  | ||||
| } // namespace QmlJS | ||||
|  | ||||
| #endif // QMLJSCODEFORMATTER_H | ||||
		Reference in New Issue
	
	Block a user