/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.ui.text.phpdoc; import java.text.BreakIterator; //import net.sourceforge.phpdt.core.ICompilationUnit; //import net.sourceforge.phpdt.core.IJavaElement; //import net.sourceforge.phpdt.core.IMethod; //import net.sourceforge.phpdt.core.ISourceRange; //import net.sourceforge.phpdt.core.IType; //import net.sourceforge.phpdt.internal.corext.util.Strings; //import net.sourceforge.phpdt.ui.CodeGeneration; //import net.sourceforge.phpdt.ui.IWorkingCopyManager; import net.sourceforge.phpdt.ui.PreferenceConstants; //import net.sourceforge.phpeclipse.PHPeclipsePlugin; import net.sourceforge.phpeclipse.ui.WebUI; //import org.eclipse.core.runtime.CoreException; //import org.eclipse.core.runtime.Preferences; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.TextUtilities; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; //import org.eclipse.ui.IWorkbenchWindow; //import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.ITextEditorExtension3; /** * Auto indent strategy for java doc comments */ public class JavaDocAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy { private String fPartitioning; /** * Creates a new Javadoc auto indent strategy for the given document * partitioning. * * @param partitioning * the document partitioning */ public JavaDocAutoIndentStrategy(String partitioning) { fPartitioning = partitioning; } private static String getLineDelimiter(IDocument document) { try { if (document.getNumberOfLines() > 1) return document.getLineDelimiter(0); } catch (BadLocationException e) { WebUI.log(e); } return System.getProperty("line.separator"); //$NON-NLS-1$ } /** * Copies the indentation of the previous line and add a star. If the * javadoc just started on this line add standard method tags and close the * javadoc. * * @param d * the document to work on * @param c * the command to deal with */ private void jdocIndentAfterNewLine(IDocument d, DocumentCommand c) { if (c.offset == -1 || d.getLength() == 0) return; try { // find start of line int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset); IRegion info = d.getLineInformationOfOffset(p); int start = info.getOffset(); // find white spaces int end = findEndOfWhiteSpace(d, start, c.offset); StringBuffer buf = new StringBuffer(c.text); if (end >= start) { // 1GEYL1R: ITPJUI:ALL - java doc edit smartness // not work for class comments // append to input String indentation = jdocExtractLinePrefix(d, d .getLineOfOffset(c.offset)); buf.append(indentation); if (end < c.offset) { if (d.getChar(end) == '/') { // javadoc started on this line buf.append(" * "); //$NON-NLS-1$ if (WebUI .getDefault() .getPreferenceStore() .getBoolean( PreferenceConstants.EDITOR_CLOSE_JAVADOCS) && isNewComment(d, c.offset, fPartitioning)) { String lineDelimiter = getLineDelimiter(d); String endTag = lineDelimiter + indentation + " */"; //$NON-NLS-1$ d.replace(c.offset, 0, endTag); //$NON-NLS-1$ // evaluate method signature //ICompilationUnit unit = getCompilationUnit(); // if // (PHPeclipsePlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_ADD_JAVADOC_TAGS) // && // unit != null) // { // try { // JavaModelUtil.reconcile(unit); // String string= createJavaDocTags(d, c, // indentation, lineDelimiter, unit); // if (string != null) { // d.replace(c.offset, 0, string); // } // } catch (CoreException e) { // // ignore // } // } } } } } c.text = buf.toString(); } catch (BadLocationException excp) { // stop work } } // private String createJavaDocTags(IDocument document, // DocumentCommand command, String indentation, String lineDelimiter, // ICompilationUnit unit) throws CoreException, BadLocationException { // IJavaElement element = unit.getElementAt(command.offset); // if (element == null) // return null; // // switch (element.getElementType()) { // case IJavaElement.TYPE: // return createTypeTags(document, command, indentation, // lineDelimiter, (IType) element); // // case IJavaElement.METHOD: // return createMethodTags(document, command, indentation, // lineDelimiter, (IMethod) element); // // default: // return null; // } // } /* * Removes start and end of a comment and corrects indentation and line * delimiters. */ // private String prepareTemplateComment(String comment, String indentation, // String lineDelimiter) { // // trim comment start and end if any // if (comment.endsWith("*/")) //$NON-NLS-1$ // comment = comment.substring(0, comment.length() - 2); // comment = comment.trim(); // if (comment.startsWith("/*")) { //$NON-NLS-1$ // if (comment.length() > 2 && comment.charAt(2) == '*') { // comment = comment.substring(3); // remove '/**' // } else { // comment = comment.substring(2); // remove '/*' // } // } // // return Strings.changeIndent(comment, 0, // // CodeFormatterUtil.getTabWidth(), indentation, lineDelimiter); // return Strings.changeIndent(comment, 0, getTabWidth(), indentation, // lineDelimiter); // } // public static int getTabWidth() { // Preferences preferences = WebUI.getDefault() // .getPluginPreferences(); // return preferences // .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); // } // private String createTypeTags(IDocument document, DocumentCommand command, // String indentation, String lineDelimiter, IType type) // throws CoreException { // String comment = CodeGeneration.getTypeComment(type // .getCompilationUnit(), type.getTypeQualifiedName('.'), // lineDelimiter); // if (comment != null) { // return prepareTemplateComment(comment.trim(), indentation, // lineDelimiter); // } // return null; // } // private String createMethodTags(IDocument document, // DocumentCommand command, String indentation, String lineDelimiter, // IMethod method) throws CoreException, BadLocationException { // IRegion partition = TextUtilities.getPartition(document, fPartitioning, // command.offset, false); // ISourceRange sourceRange = method.getSourceRange(); // if (sourceRange == null // || sourceRange.getOffset() != partition.getOffset()) // return null; // // // IMethod inheritedMethod= getInheritedMethod(method); // // String comment= CodeGeneration.getMethodComment(method, // // inheritedMethod, lineDelimiter); // // if (comment != null) { // // comment= comment.trim(); // // boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$ // // boolean isJavaDoc= partition.getLength() >= 3 && // // document.get(partition.getOffset(), 3).equals("/**"); //$NON-NLS-1$ // // if (javadocComment == isJavaDoc) { // // return prepareTemplateComment(comment, indentation, lineDelimiter); // // } // // } // return null; // } /** * Returns the method inherited from, null if method is newly * defined. */ // private static IMethod getInheritedMethod(IMethod method) throws // JavaModelException { // IType declaringType= method.getDeclaringType(); // ITypeHierarchy typeHierarchy= // SuperTypeHierarchyCache.getTypeHierarchy(declaringType); // return JavaModelUtil.findMethodDeclarationInHierarchy(typeHierarchy, // declaringType, // method.getElementName(), method.getParameterTypes(), // method.isConstructor()); // } protected void jdocIndentForCommentEnd(IDocument d, DocumentCommand c) { if (c.offset < 2 || d.getLength() == 0) { return; } try { if ("* ".equals(d.get(c.offset - 2, 2))) { //$NON-NLS-1$ // modify document command c.length++; c.offset--; } } catch (BadLocationException excp) { // stop work } } /** * Guesses if the command operates within a newly created javadoc comment or * not. If in doubt, it will assume that the javadoc is new. */ private static boolean isNewComment(IDocument document, int commandOffset, String partitioning) { try { int lineIndex = document.getLineOfOffset(commandOffset) + 1; if (lineIndex >= document.getNumberOfLines()) return true; IRegion line = document.getLineInformation(lineIndex); ITypedRegion partition = TextUtilities.getPartition(document, partitioning, commandOffset, false); int partitionEnd = partition.getOffset() + partition.getLength(); if (line.getOffset() >= partitionEnd) return false; if (document.getLength() == partitionEnd) return true; // partition goes to end of document - probably // a new comment String comment = document.get(partition.getOffset(), partition .getLength()); if (comment.indexOf("/*", 2) != -1) //$NON-NLS-1$ return true; // enclosed another comment -> probably a new // comment return false; } catch (BadLocationException e) { return false; } } private boolean isSmartMode() { IWorkbenchPage page = WebUI.getActivePage(); if (page != null) { IEditorPart part = page.getActiveEditor(); if (part instanceof ITextEditorExtension3) { ITextEditorExtension3 extension = (ITextEditorExtension3) part; return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT; } } return false; } /* * @see IAutoIndentStrategy#customizeDocumentCommand */ public void customizeDocumentCommand(IDocument document, DocumentCommand command) { if (!isSmartMode()) return; try { if (command.text != null && command.length == 0) { String[] lineDelimiters = document.getLegalLineDelimiters(); int index = TextUtilities .endsWith(lineDelimiters, command.text); if (index > -1) { // ends with line delimiter if (lineDelimiters[index].equals(command.text)) // just the line delimiter jdocIndentAfterNewLine(document, command); return; } } if (command.text != null && command.text.equals("/")) { //$NON-NLS-1$ jdocIndentForCommentEnd(document, command); return; } ITypedRegion partition = TextUtilities.getPartition(document, fPartitioning, command.offset, true); int partitionStart = partition.getOffset(); int partitionEnd = partition.getLength() + partitionStart; String text = command.text; int offset = command.offset; int length = command.length; // partition change final int PREFIX_LENGTH = "/*".length(); //$NON-NLS-1$ final int POSTFIX_LENGTH = "*/".length(); //$NON-NLS-1$ if ((offset < partitionStart + PREFIX_LENGTH || offset + length > partitionEnd - POSTFIX_LENGTH) || text != null && text.length() >= 2 && ((text.indexOf("*/") != -1) || (document.getChar(offset) == '*' && text.startsWith("/")))) //$NON-NLS-1$ //$NON-NLS-2$ return; if (command.text == null || command.text.length() == 0) jdocHandleBackspaceDelete(document, command); else if (command.text != null && command.length == 0 && command.text.length() > 0) jdocWrapParagraphOnInsert(document, command); } catch (BadLocationException e) { WebUI.log(e); } } private void flushCommand(IDocument document, DocumentCommand command) throws BadLocationException { if (!command.doit) return; document.replace(command.offset, command.length, command.text); command.doit = false; if (command.text != null) command.offset += command.text.length(); command.length = 0; command.text = null; } protected void jdocWrapParagraphOnInsert(IDocument document, DocumentCommand command) throws BadLocationException { // Assert.isTrue(command.length == 0); // Assert.isTrue(command.text != null && command.text.length() == 1); if (!getPreferenceStore().getBoolean( PreferenceConstants.EDITOR_FORMAT_JAVADOCS)) return; int line = document.getLineOfOffset(command.offset); IRegion region = document.getLineInformation(line); int lineOffset = region.getOffset(); int lineLength = region.getLength(); String lineContents = document.get(lineOffset, lineLength); StringBuffer buffer = new StringBuffer(lineContents); int start = command.offset - lineOffset; int end = command.length + start; buffer.replace(start, end, command.text); // handle whitespace if (command.text != null && command.text.length() != 0 && command.text.trim().length() == 0) { String endOfLine = document.get(command.offset, lineOffset + lineLength - command.offset); // end of line if (endOfLine.length() == 0) { // move caret to next line flushCommand(document, command); if (isLineTooShort(document, line)) { int[] caretOffset = { command.offset }; jdocWrapParagraphFromLine(document, line, caretOffset, false); command.offset = caretOffset[0]; return; } // move caret to next line if possible if (line < document.getNumberOfLines() - 1 && isJavaDocLine(document, line + 1)) { String lineDelimiter = document.getLineDelimiter(line); String nextLinePrefix = jdocExtractLinePrefix(document, line + 1); command.offset += lineDelimiter.length() + nextLinePrefix.length(); } return; // inside whitespace at end of line } else if (endOfLine.trim().length() == 0) { // simply insert space return; } } // change in prefix region String prefix = jdocExtractLinePrefix(document, line); boolean wrapAlways = command.offset >= lineOffset && command.offset <= lineOffset + prefix.length(); // must insert the text now because it may include whitepace flushCommand(document, command); if (wrapAlways || calculateDisplayedWidth(buffer.toString()) > getMargin() || isLineTooShort(document, line)) { int[] caretOffset = { command.offset }; jdocWrapParagraphFromLine(document, line, caretOffset, wrapAlways); if (!wrapAlways) command.offset = caretOffset[0]; } } /** * Method jdocWrapParagraphFromLine. * * @param document * @param line * @param always */ private void jdocWrapParagraphFromLine(IDocument document, int line, int[] caretOffset, boolean always) throws BadLocationException { String indent = jdocExtractLinePrefix(document, line); if (!always) { if (!indent.trim().startsWith("*")) //$NON-NLS-1$ return; if (indent.trim().startsWith("*/")) //$NON-NLS-1$ return; if (!isLineTooLong(document, line) && !isLineTooShort(document, line)) return; } boolean caretRelativeToParagraphOffset = false; int caret = caretOffset[0]; int caretLine = document.getLineOfOffset(caret); int lineOffset = document.getLineOffset(line); int paragraphOffset = lineOffset + indent.length(); if (paragraphOffset < caret) { caret -= paragraphOffset; caretRelativeToParagraphOffset = true; } else { caret -= lineOffset; } StringBuffer buffer = new StringBuffer(); int currentLine = line; while (line == currentLine || isJavaDocLine(document, currentLine)) { if (buffer.length() != 0 && !Character.isWhitespace(buffer .charAt(buffer.length() - 1))) { buffer.append(' '); if (currentLine <= caretLine) { // in this case caretRelativeToParagraphOffset is always // true ++caret; } } String string = getLineContents(document, currentLine); buffer.append(string); currentLine++; } String paragraph = buffer.toString(); if (paragraph.trim().length() == 0) return; caretOffset[0] = caretRelativeToParagraphOffset ? caret : 0; String delimiter = document.getLineDelimiter(0); String wrapped = formatParagraph(paragraph, caretOffset, indent, delimiter, getMargin()); int beginning = document.getLineOffset(line); int end = document.getLineOffset(currentLine); document.replace(beginning, end - beginning, wrapped.toString()); caretOffset[0] = caretRelativeToParagraphOffset ? caretOffset[0] + beginning : caret + beginning; } /** * Line break iterator to handle whitespaces as first class citizens. */ private static class LineBreakIterator { private final String fString; private final BreakIterator fIterator = BreakIterator.getLineInstance(); private int fStart; private int fEnd; private int fBufferedEnd; public LineBreakIterator(String string) { fString = string; fIterator.setText(string); } public int first() { fBufferedEnd = -1; fStart = fIterator.first(); return fStart; } public int next() { if (fBufferedEnd != -1) { fStart = fEnd; fEnd = fBufferedEnd; fBufferedEnd = -1; return fEnd; } fStart = fEnd; fEnd = fIterator.next(); if (fEnd == BreakIterator.DONE) return fEnd; final String string = fString.substring(fStart, fEnd); // whitespace if (string.trim().length() == 0) return fEnd; final String word = string.trim(); if (word.length() == string.length()) return fEnd; // suspected whitespace fBufferedEnd = fEnd; return fStart + word.length(); } } /** * Formats a paragraph, using break iterator. * * @param offset * an offset within the paragraph, which will be updated with * respect to formatting. */ private static String formatParagraph(String paragraph, int[] offset, String prefix, String lineDelimiter, int margin) { LineBreakIterator iterator = new LineBreakIterator(paragraph); StringBuffer paragraphBuffer = new StringBuffer(); StringBuffer lineBuffer = new StringBuffer(); StringBuffer whiteSpaceBuffer = new StringBuffer(); int index = offset[0]; int indexBuffer = -1; // line delimiter could be null if (lineDelimiter == null) lineDelimiter = ""; //$NON-NLS-1$ for (int start = iterator.first(), end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator .next()) { String word = paragraph.substring(start, end); // word is whitespace if (word.trim().length() == 0) { whiteSpaceBuffer.append(word); // first word of line is always appended } else if (lineBuffer.length() == 0) { lineBuffer.append(prefix); lineBuffer.append(whiteSpaceBuffer.toString()); lineBuffer.append(word); } else { String line = lineBuffer.toString() + whiteSpaceBuffer.toString() + word.toString(); // margin exceeded if (calculateDisplayedWidth(line) > margin) { // flush line buffer and wrap paragraph paragraphBuffer.append(lineBuffer.toString()); paragraphBuffer.append(lineDelimiter); lineBuffer.setLength(0); lineBuffer.append(prefix); lineBuffer.append(word); // flush index buffer if (indexBuffer != -1) { offset[0] = indexBuffer; // correct for caret in whitespace at the end of line if (whiteSpaceBuffer.length() != 0 && index < start && index >= start - whiteSpaceBuffer.length()) offset[0] -= (index - (start - whiteSpaceBuffer .length())); indexBuffer = -1; } whiteSpaceBuffer.setLength(0); // margin not exceeded } else { lineBuffer.append(whiteSpaceBuffer.toString()); lineBuffer.append(word); whiteSpaceBuffer.setLength(0); } } if (index >= start && index < end) { indexBuffer = paragraphBuffer.length() + lineBuffer.length() + (index - start); if (word.trim().length() != 0) indexBuffer -= word.length(); } } // flush line buffer paragraphBuffer.append(lineBuffer.toString()); paragraphBuffer.append(lineDelimiter); // flush index buffer if (indexBuffer != -1) offset[0] = indexBuffer; // last position is not returned by break iterator else if (offset[0] == paragraph.length()) offset[0] = paragraphBuffer.length() - lineDelimiter.length(); return paragraphBuffer.toString(); } private static IPreferenceStore getPreferenceStore() { return WebUI.getDefault().getPreferenceStore(); } /** * Returns the displayed width of a string, taking in account the displayed * tab width. The result can be compared against the print margin. */ private static int calculateDisplayedWidth(String string) { int tabWidth = getPreferenceStore() .getInt( AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); if (tabWidth <= 0) { tabWidth = 2; } int column = 0; for (int i = 0; i < string.length(); i++) if ('\t' == string.charAt(i)) column += tabWidth - (column % tabWidth); else column++; return column; } private String jdocExtractLinePrefix(IDocument d, int line) throws BadLocationException { IRegion region = d.getLineInformation(line); int lineOffset = region.getOffset(); int index = findEndOfWhiteSpace(d, lineOffset, lineOffset + d.getLineLength(line)); if (d.getChar(index) == '*') { index++; if (index != lineOffset + region.getLength() && d.getChar(index) == ' ') index++; } return d.get(lineOffset, index - lineOffset); } private String getLineContents(IDocument d, int line) throws BadLocationException { int offset = d.getLineOffset(line); int length = d.getLineLength(line); String lineDelimiter = d.getLineDelimiter(line); if (lineDelimiter != null) length = length - lineDelimiter.length(); String lineContents = d.get(offset, length); int trim = jdocExtractLinePrefix(d, line).length(); return lineContents.substring(trim); } private static String getLine(IDocument document, int line) throws BadLocationException { IRegion region = document.getLineInformation(line); return document.get(region.getOffset(), region.getLength()); } /** * Returns true if the javadoc line is too short, * false otherwise. */ private boolean isLineTooShort(IDocument document, int line) throws BadLocationException { if (!isJavaDocLine(document, line + 1)) return false; String nextLine = getLineContents(document, line + 1); if (nextLine.trim().length() == 0) return false; return true; } /** * Returns true if the line is too long, false * otherwise. */ private boolean isLineTooLong(IDocument document, int line) throws BadLocationException { String lineContents = getLine(document, line); return calculateDisplayedWidth(lineContents) > getMargin(); } private static int getMargin() { return getPreferenceStore() .getInt( AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); } private static final String[] fgInlineTags = { "", "", "", "", "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ }; private boolean isInlineTag(String string) { for (int i = 0; i < fgInlineTags.length; i++) if (string.startsWith(fgInlineTags[i])) return true; return false; } /** * returns true if the specified line is part of a paragraph and should be * merged with the previous line. */ private boolean isJavaDocLine(IDocument document, int line) throws BadLocationException { if (document.getNumberOfLines() < line) return false; int offset = document.getLineOffset(line); int length = document.getLineLength(line); int firstChar = findEndOfWhiteSpace(document, offset, offset + length); length -= firstChar - offset; String lineContents = document.get(firstChar, length); String prefix = lineContents.trim(); if (!prefix.startsWith("*") || prefix.startsWith("*/")) //$NON-NLS-1$ //$NON-NLS-2$ return false; lineContents = lineContents.substring(1).trim().toLowerCase(); // preserve empty lines if (lineContents.length() == 0) return false; // preserve @TAGS if (lineContents.startsWith("@")) //$NON-NLS-1$ return false; // preserve HTML tags which are not inline if (lineContents.startsWith("<") && !isInlineTag(lineContents)) //$NON-NLS-1$ return false; return true; } protected void jdocHandleBackspaceDelete(IDocument document, DocumentCommand c) { if (!getPreferenceStore().getBoolean( PreferenceConstants.EDITOR_FORMAT_JAVADOCS)) return; try { String text = document.get(c.offset, c.length); int line = document.getLineOfOffset(c.offset); int lineOffset = document.getLineOffset(line); // erase line delimiter String lineDelimiter = document.getLineDelimiter(line); if (lineDelimiter != null && lineDelimiter.equals(text)) { String prefix = jdocExtractLinePrefix(document, line + 1); // strip prefix if any if (prefix.length() > 0) { int length = document.getLineDelimiter(line).length() + prefix.length(); document.replace(c.offset, length, null); c.doit = false; c.length = 0; return; } // backspace: beginning of a javadoc line } else if (document.getChar(c.offset - 1) == '*' && jdocExtractLinePrefix(document, line).length() - 1 >= c.offset - lineOffset) { lineDelimiter = document.getLineDelimiter(line - 1); String prefix = jdocExtractLinePrefix(document, line); int length = (lineDelimiter != null ? lineDelimiter.length() : 0) + prefix.length(); document.replace(c.offset - length + 1, length, null); c.doit = false; c.offset -= length - 1; c.length = 0; return; } else { document.replace(c.offset, c.length, null); c.doit = false; c.length = 0; } } catch (BadLocationException e) { WebUI.log(e); } try { int line = document.getLineOfOffset(c.offset); int lineOffset = document.getLineOffset(line); String prefix = jdocExtractLinePrefix(document, line); boolean always = c.offset > lineOffset && c.offset <= lineOffset + prefix.length(); int[] caretOffset = { c.offset }; jdocWrapParagraphFromLine(document, document .getLineOfOffset(c.offset), caretOffset, always); c.offset = caretOffset[0]; } catch (BadLocationException e) { WebUI.log(e); } } /** * Returns the compilation unit of the CompilationUnitEditor invoking the * AutoIndentStrategy, might return null on error. */ // private static ICompilationUnit getCompilationUnit() { // // IWorkbenchWindow window = PlatformUI.getWorkbench() // .getActiveWorkbenchWindow(); // if (window == null) // return null; // // IWorkbenchPage page = window.getActivePage(); // if (page == null) // return null; // // IEditorPart editor = page.getActiveEditor(); // if (editor == null) // return null; // // IWorkingCopyManager manager = WebUI.getDefault() // .getWorkingCopyManager(); // ICompilationUnit unit = manager.getWorkingCopy(editor.getEditorInput()); // if (unit == null) // return null; // // return unit; // } }