/**
* Copyright (C) 2015 drrb
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.drrb.rust.netbeans.highlighting;
import com.github.drrb.rust.netbeans.RustLanguage;
import com.github.drrb.rust.netbeans.configuration.RustConfiguration;
import com.github.drrb.rust.netbeans.parsing.NetbeansRustParser.NetbeansRustParserResult;
import com.github.drrb.rust.netbeans.cargo.Crate;
import com.github.drrb.rust.netbeans.project.RustProject;
import com.github.drrb.rust.netbeans.rustbridge.RustCompiler;
import com.github.drrb.rust.netbeans.rustbridge.RustParseMessage;
import static com.github.drrb.rust.netbeans.rustbridge.RustParseMessage.Level.HELP;
import com.github.drrb.rust.netbeans.util.GsfUtilitiesHack;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.ParserResultTask;
import org.netbeans.modules.parsing.spi.Scheduler;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.modules.parsing.spi.SchedulerTask;
import org.netbeans.modules.parsing.spi.TaskFactory;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
import org.netbeans.spi.editor.hints.HintsController;
import org.netbeans.spi.editor.hints.Severity;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
import org.openide.util.RequestProcessor;
/**
*
*/
public class RustCompileErrorHighlighter extends ParserResultTask<NetbeansRustParserResult> {
private static final Logger LOG = Logger.getLogger(RustCompileErrorHighlighter.class.getName());
private static final RequestProcessor EXECUTOR = new RequestProcessor("Rust Compile", 12);
@MimeRegistration(mimeType = RustLanguage.MIME_TYPE, service = TaskFactory.class)
public static class Factory extends TaskFactory {
@Override
public Collection<? extends SchedulerTask> create(Snapshot snapshot) {
return Collections.singleton(new RustCompileErrorHighlighter());
}
}
@Override
public void run(NetbeansRustParserResult parseResult, SchedulerEvent event) {
try {
if (parseResult.getResult().isFailure()) {
return;
}
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
final Snapshot snapshot = parseResult.getSnapshot();
EXECUTOR.post(new Runnable() {
@Override
public void run() {
compile(snapshot);
}
});
}
@VisibleForTesting
public void compile(Snapshot snapshot) {
//TODO: this approach makes highlighting slow if project is more than
// a handful of files. We need some caching, which will probably require
// changes to be made to rustc.
try {
FileObject sourceFile = snapshot.getSource().getFileObject();
//TODO: What if it's not in a project, or it's in a aproject of a different type?
RustProject project = FileOwnerQuery.getOwner(sourceFile).getLookup().lookup(RustProject.class);
Crate crate = project.getCargoConfig().getOwningCrate(sourceFile);
FileObject crateFile = crate.getFile();
// If the crate is the one being edited, use its snapshot (i.e. the
// version that is currently in the editor.
// TODO: this is a workaround for rustc only letting us provide the
// root file as a string (the others get read directly from disk).
// Ideally we'd like to be able to have Java read the files and
// pass them into Rust via a callback so that we can use snapshots
// for all files.
String source;
if (crateFile.equals(sourceFile)) {
source = snapshot.getText().toString();
} else {
source = crateFile.asText(UTF_8.name());
}
List<RustParseMessage> messages = new RustCompiler().compile(FileUtil.toFile(crateFile), source, FileUtil.toFile(sourceFile), RustConfiguration.get().getLibrariesPaths());
StyledDocument document = GsfUtilitiesHack.getDocument(sourceFile, true);
List<ErrorDescription> errors = getErrors(messages, document);
setErrors(document, "rust-compile-errors", errors);
} catch (BadLocationException | IOException ex) {
Exceptions.printStackTrace(ex);
}
}
@VisibleForTesting
protected void setErrors(Document document, String layerName, List<ErrorDescription> errors) {
HintsController.setErrors(document, layerName, errors);
}
@Override
public int getPriority() {
// After parse error highlighting runs
return 1000;
}
@Override
public Class<? extends Scheduler> getSchedulerClass() {
return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER;
}
@Override
public void cancel() {
//TODO
}
@VisibleForTesting
protected List<ErrorDescription> getErrors(List<RustParseMessage> messages, StyledDocument document) throws BadLocationException {
List<ErrorDescription> errors = new LinkedList<>();
for (RustParseMessage message : messages) {
if (message.getLevel() != HELP) {
errors.add(toErrorDescription(message, document));
}
}
return errors;
}
private ErrorDescription toErrorDescription(RustParseMessage message, StyledDocument document) throws BadLocationException {
int startOffset = NbDocument.findLineOffset(document, message.getStartLine() - 1) + message.getStartCol();
int endOffset = NbDocument.findLineOffset(document, message.getEndLine() - 1) + message.getEndCol();
return ErrorDescriptionFactory.createErrorDescription(
message.getLevel() == RustParseMessage.Level.WARNING ? Severity.WARNING : Severity.ERROR,
message.getMessage(),
document,
document.createPosition(startOffset),
document.createPosition(endOffset));
}
}