blob: b9bdda436f8edbb374208494fe11def2a71c13fa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.editor.indent;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.StyledDocument;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.SimpleValueNames;
import org.netbeans.modules.editor.indent.api.Indent;
import org.netbeans.modules.editor.indent.api.Reformat;
/**
* Indentation and code reformatting services for a swing text document.
*
* @author Miloslav Metelka
*/
public final class IndentImpl {
// -J-Dorg.netbeans.modules.editor.indent.IndentImpl=FINE
private static final Logger LOG = Logger.getLogger(IndentImpl.class.getName());
public static IndentImpl get(Document doc) {
IndentImpl indentImpl = (IndentImpl)doc.getProperty(IndentImpl.class);
if (indentImpl == null) {
indentImpl = new IndentImpl(doc);
doc.putProperty(IndentImpl.class, indentImpl);
}
// XXX: formatting infra cleanup
// indentImpl.refresh();
return indentImpl;
}
private final Document doc;
private final Preferences prefs;
private Indent indent;
private Reformat reformat;
private TaskHandler indentHandler;
private TaskHandler reformatHandler;
// XXX: formatting infra cleanup
// private Formatter defaultFormatter;
private Thread indentLockThread;
private int indentLockExtraDepth;
private final Object indentLock = new Object();
private Thread reformatLockThread;
private int reformatLockExtraDepth;
private final Object reformatLock = new Object();
public IndentImpl(Document doc) {
this.doc = doc;
String mimeType = (String)doc.getProperty("mimeType"); //NOI18N
this.prefs = mimeType != null ? MimeLookup.getLookup(mimeType).lookup(Preferences.class) : null;
}
public Document document() {
return doc;
}
public Indent getIndent() {
return indent;
}
public void setIndent(Indent indent) {
this.indent = indent;
}
public Reformat getReformat() {
return reformat;
}
public void setReformat(Reformat reformat) {
this.reformat = reformat;
}
// XXX: formatting infra cleanup
// void setDefaultFormatter(Formatter defaultFormatter) {
// this.defaultFormatter = defaultFormatter;
// }
//
// void refresh() {
// if (defaultFormatter == null) {
// if (doc instanceof BaseDocument) {
// defaultFormatter = ((BaseDocument)doc).getLegacyFormatter();
// }
// }
// }
public void indentLock() {
synchronized(indentLock) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("indentLock() on " + this);
}
Thread currentThread = Thread.currentThread();
while (indentLockThread != null) {
if (currentThread == indentLockThread) {
indentLockExtraDepth++; // Extra inner lock
return;
}
try {
indentLock.wait();
} catch (InterruptedException e) {
throw new Error("Interrupted at acquiring indent-lock");
}
}
indentLockThread = currentThread;
indentHandler = new TaskHandler(true, doc);
try {
if (indentHandler.collectTasks()) {
indentHandler.lock();
}
} catch (Exception e) {
indentUnlock();
throw e;
}
}
}
public void indentUnlock() {
synchronized(indentLock) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("indentUnlock() on " + this);
}
Thread currentThread = Thread.currentThread();
if (currentThread != indentLockThread) {
throw new IllegalStateException("Invalid indentUnlock(): current-thread=" + // NOI18N
currentThread + ", lockThread=" + indentLockThread + ", lockExtraDepth=" + indentLockExtraDepth); // NOI18N
}
if (indentLockExtraDepth == 0) {
indentHandler.unlock();
indentHandler = null;
indentLockThread = null;
indentLock.notifyAll();
} else {
indentLockExtraDepth--;
}
}
}
public TaskHandler indentHandler() {
return indentHandler;
}
public void reformatLock() {
synchronized(reformatLock) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("reformatLock() on " + this);
}
Thread currentThread = Thread.currentThread();
while (reformatLockThread != null) {
if (currentThread == reformatLockThread) {
reformatLockExtraDepth++; // Extra inner lock
return;
}
try {
reformatLock.wait();
} catch (InterruptedException e) {
throw new Error("Interrupted at acquiring reformat-lock");
}
}
reformatLockThread = currentThread;
reformatHandler = new TaskHandler(false, doc);
try {
if (reformatHandler.collectTasks()) {
reformatHandler.lock();
}
} catch (Exception e) {
reformatUnlock();
throw e;
}
}
}
public void reformatUnlock() {
synchronized(reformatLock) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("reformatUnlock() on " + this);
}
Thread currentThread = Thread.currentThread();
if (currentThread != reformatLockThread) {
throw new IllegalStateException("Invalid reformatUnlock(): current-thread=" + // NOI18N
currentThread + ", lockThread=" + reformatLockThread + ", lockExtraDepth=" + reformatLockExtraDepth); // NOI18N
}
if (reformatLockExtraDepth == 0) {
reformatHandler.unlock();
reformatHandler = null;
reformatLockThread = null;
reformatLock.notifyAll();
} else {
reformatLockExtraDepth--;
}
}
}
public TaskHandler reformatHandler() {
return reformatHandler;
}
public int reindent(int startOffset, int endOffset, int caretOffset, boolean indentNewLine) throws BadLocationException {
if (startOffset > endOffset) {
throw new IllegalArgumentException("startOffset=" + startOffset + " > endOffset=" + endOffset); // NOI18N
}
boolean runUnlocked = false;
if (indentHandler == null) {
LOG.log(Level.SEVERE, null, new Exception("Not locked. Use Indent.lock().")); // NOI18N
runUnlocked = true;
// Attempt to call the tasks unlocked since now it's too late to lock (doc's lock already taken).
indentHandler = new TaskHandler(true, doc);
}
try {
if (runUnlocked) {
indentHandler.collectTasks();
}
indentHandler.setCaretOffset(caretOffset);
// Find begining of line
Element lineRootElem = lineRootElement(doc);
// Correct the start offset to point to the begining of the start line
// XXX: formatting infra cleanup
// boolean done = false;
if (indentHandler.hasItems()) {
// When indenting newline first insert a plain newline
if (indentNewLine) {
doc.insertString(startOffset, "\n", null);
// Adjust start and end offsets after the inserted newline
startOffset++;
endOffset++;
// Fix for Enter on first line - if it would have offset 0 the position would stay
// at the begining of the document
if (indentHandler.caretOffset() == 0) {
indentHandler.setCaretOffset(1);
}
}
if (prefs == null || prefs.getBoolean(SimpleValueNames.ENABLE_INDENTATION, true)) {
int startLineIndex = lineRootElem.getElementIndex(startOffset);
Element lineElem = lineRootElem.getElement(startLineIndex);
int startLineOffset = lineElem.getStartOffset();
// Find ending line element - by default use the same as for start offset
if (endOffset > lineElem.getEndOffset()) { // need to get a different line element
int endLineIndex = lineRootElem.getElementIndex(endOffset);
lineElem = lineRootElem.getElement(endLineIndex);
// Check if the given endOffset ends right after line's newline (in fact at the begining of the next line)
if (endLineIndex > 0 && lineElem.getStartOffset() == endOffset) {
endLineIndex--;
lineElem = lineRootElem.getElement(endLineIndex);
}
}
// Create context from begining of the start line till the end of the end line.
indentHandler.setGlobalBounds(
doc.createPosition(startLineOffset),
doc.createPosition(lineElem.getEndOffset() - 1));
// Perform whole reindent on top and possibly embedded levels
indentHandler.runTasks();
}
// XXX: formatting infra cleanup
// done = true;
}
// XXX: formatting infra cleanup
// // Fallback to Formatter
// if (!done && doc instanceof BaseDocument && defaultFormatter != null) {
// if (indentNewLine) {
// if (LOG.isLoggable(Level.FINE)) {
// LOG.fine("Defaulting reindent() to indentNewLine() in legacy formatter " + // NOI18N
// defaultFormatter + '\n');
// }
// // Fallback to indentNewLine() will insert '\n'
// int newCaretOffset = defaultFormatter.indentNewLine(doc, caretOffset);
// indentHandler.setCaretOffset(newCaretOffset);
// } else { // Indent line
// // Original formatter does not have reindentation of multiple lines
// // so reformat start line and continue for each line.
// Position endPos = doc.createPosition(endOffset);
// if (LOG.isLoggable(Level.FINE)) {
// LOG.fine("Defaulting reindent() to indentLine() in legacy formatter " + // NOI18N
// defaultFormatter + '\n');
// }
// do {
// startOffset = defaultFormatter.indentLine(doc, startOffset);
// int startLineIndex = lineRootElem.getElementIndex(startOffset) + 1;
// if (startLineIndex >= lineRootElem.getElementCount())
// break;
// Element lineElem = lineRootElem.getElement(startLineIndex);
// startOffset = lineElem.getStartOffset(); // Move to next line
// } while (startOffset < endPos.getOffset());
// }
// }
return indentHandler.caretOffset();
} finally {
if (runUnlocked)
indentHandler = null;
}
}
public void reformat(int startOffset, int endOffset, int caretOffset) throws BadLocationException {
if (startOffset > endOffset) {
throw new IllegalArgumentException("startOffset=" + startOffset + " > endOffset=" + endOffset); // NOI18N
}
boolean runUnlocked = false;
if (reformatHandler == null) { //
LOG.log(Level.SEVERE, "Not locked. Use Reformat.lock().", new Exception()); // NOI18N
// Attempt to call the tasks unlocked since now it's too late to lock (doc's lock already taken).
runUnlocked = true;
reformatHandler = new TaskHandler(false, doc);
}
try {
if (runUnlocked) {
reformatHandler.collectTasks();
}
reformatHandler.setCaretOffset(caretOffset);
// XXX: formatting infra cleanup
// boolean done = false;
if (reformatHandler.hasItems()) {
reformatHandler.setGlobalBounds(
doc.createPosition(startOffset),
doc.createPosition(endOffset));
// Run top and embedded reformatting
reformatHandler.runTasks();
// Perform reformatting of the top section and possible embedded sections
// XXX: formatting infra cleanup
// done = true;
}
// XXX: formatting infra cleanup
// // Fallback to Formatter
// if (!done && doc instanceof BaseDocument && defaultFormatter != null) {
// if (LOG.isLoggable(Level.FINE)) {
// LOG.fine("Defaulting reformat() to reformat() in legacy formatter " + // NOI18N
// defaultFormatter + '\n');
// }
// BaseDocument bdoc = (BaseDocument)doc;
// defaultFormatter.reformat(bdoc, startOffset, endOffset);
// }
} finally {
if (runUnlocked)
reformatHandler = null;
}
}
public static Element lineRootElement(Document doc) {
return (doc instanceof StyledDocument)
? ((StyledDocument)doc).getParagraphElement(0).getParentElement()
: doc.getDefaultRootElement();
}
public static void checkOffsetInDocument(Document doc, int offset) throws BadLocationException {
if (offset < 0)
throw new BadLocationException("offset=" + offset + " < 0", offset); // NOI18N
if (offset > doc.getLength())
throw new BadLocationException("offset=" + offset + " > doc.getLength()=" + doc.getLength(), offset);
}
}