1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-09-03 05:33:33 +02:00

Bug 481126 - QML Imports Working in Editor

Hooked up the extra logic needed to get Tern-QML's imports to work in
the QML File Editor.

Change-Id: I6fa222223ca8b6b177e4004e48f2f1863ab4d7b4
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-12-04 17:08:10 -05:00 committed by Gerrit Code Review @ Eclipse.org
parent 3a2cb0431e
commit 42e3859b68
8 changed files with 186 additions and 18 deletions

View file

@ -28,9 +28,7 @@ public class NashornTests {
int pos = code.indexOf('|'); int pos = code.indexOf('|');
code = code.substring(0, pos) + code.substring(pos + 1); code = code.substring(0, pos) + code.substring(pos + 1);
analyzer.addFile("test1.qml", code); Collection<QMLTernCompletion> QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos);
Collection<QMLTernCompletion> QMLTernCompletions = analyzer.getCompletions("test1.qml", pos);
Map<String, QMLTernCompletion> set = new HashMap<>(); Map<String, QMLTernCompletion> set = new HashMap<>();
Set<String> unexpected = new HashSet<>(); Set<String> unexpected = new HashSet<>();

View file

@ -12,6 +12,7 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -24,6 +25,13 @@ import javax.script.ScriptEngineManager;
import javax.script.ScriptException; import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.core.Activator; import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
@SuppressWarnings("nls") @SuppressWarnings("nls")
public class QMLAnalyzer { public class QMLAnalyzer {
@ -65,19 +73,51 @@ public class QMLAnalyzer {
invoke.invokeMethod(defs, "push", engine.get("ecma5defs")); invoke.invokeMethod(defs, "push", engine.get("ecma5defs"));
options.put("defs", defs); options.put("defs", defs);
ResolveDirectory resolveDirectory = (file, pathString) -> {
String filename = (String) file.get("name");
int slash = filename.lastIndexOf('/');
String fileDirectory = slash >= 0 ? filename.substring(0, slash + 1) : filename;
if (pathString == null) {
return fileDirectory;
}
IPath path = Path.fromOSString(pathString);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
if (!path.isAbsolute()) {
IResource res = root.findMember(fileDirectory);
if (res instanceof IContainer) {
IContainer dir = (IContainer) res;
res = dir.findMember(path);
if (res != null) {
String p = res.getFullPath().toString().substring(1);
if (!p.isEmpty() && !p.endsWith("/")) {
p += "/";
}
return p;
}
}
}
return pathString;
};
options.put("resolveDirectory", invoke.invokeFunction("resolveDirectory", resolveDirectory));
synchronized (this) { synchronized (this) {
tern = invoke.invokeFunction("newTernServer", options); tern = invoke.invokeFunction("newTernServer", options);
notifyAll(); notifyAll();
} }
} }
@FunctionalInterface
public interface ResolveDirectory {
public String resolveDirectory(Bindings file, String path);
}
private Object load(String file) throws ScriptException, IOException { private Object load(String file) throws ScriptException, IOException {
URL scriptURL = Activator.getDefault().getBundle().getEntry(file); URL scriptURL = Activator.getDefault().getBundle().getEntry(file);
if (scriptURL == null) { if (scriptURL == null) {
throw new FileNotFoundException(file); throw new FileNotFoundException(file);
} }
engine.getContext().setAttribute(ScriptEngine.FILENAME, file, ScriptContext.ENGINE_SCOPE); engine.getContext().setAttribute(ScriptEngine.FILENAME, file, ScriptContext.ENGINE_SCOPE);
return engine.eval(new BufferedReader(new InputStreamReader(scriptURL.openStream()))); return engine.eval(new BufferedReader(new InputStreamReader(scriptURL.openStream(), StandardCharsets.UTF_8)));
} }
private void waitUntilLoaded() { private void waitUntilLoaded() {
@ -93,8 +133,9 @@ public class QMLAnalyzer {
} }
} }
@FunctionalInterface
public interface RequestCallback { public interface RequestCallback {
void callback(Object err, Object data); void callback(Object err, Bindings data);
} }
public void addFile(String fileName, String code) throws NoSuchMethodException, ScriptException { public void addFile(String fileName, String code) throws NoSuchMethodException, ScriptException {
@ -102,9 +143,21 @@ public class QMLAnalyzer {
invoke.invokeMethod(tern, "addFile", fileName, code); invoke.invokeMethod(tern, "addFile", fileName, code);
} }
public Collection<QMLTernCompletion> getCompletions(String fileName, int pos) public void deleteFile(String fileName) throws NoSuchMethodException, ScriptException {
waitUntilLoaded();
invoke.invokeMethod(tern, "delFile", fileName);
}
public Collection<QMLTernCompletion> getCompletions(String fileName, String text, int pos)
throws NoSuchMethodException, ScriptException { throws NoSuchMethodException, ScriptException {
waitUntilLoaded(); waitUntilLoaded();
Bindings file = engine.createBindings();
file.put("type", "full");
file.put("name", fileName);
file.put("text", text);
Bindings files = (Bindings) engine.eval("new Array()");
invoke.invokeMethod(files, "push", file);
Bindings query = engine.createBindings(); Bindings query = engine.createBindings();
query.put("type", "completions"); query.put("type", "completions");
query.put("file", fileName); query.put("file", fileName);
@ -119,6 +172,7 @@ public class QMLAnalyzer {
query.put("includeKeywords", true); query.put("includeKeywords", true);
query.put("guess", false); query.put("guess", false);
Bindings request = engine.createBindings(); Bindings request = engine.createBindings();
request.put("files", files);
request.put("query", query); request.put("query", query);
List<QMLTernCompletion> completions = new ArrayList<>(); List<QMLTernCompletion> completions = new ArrayList<>();
@ -129,7 +183,7 @@ public class QMLAnalyzer {
} else { } else {
try { try {
for (Bindings completion : (Bindings[]) invoke.invokeMethod(engine.get("Java"), "to", for (Bindings completion : (Bindings[]) invoke.invokeMethod(engine.get("Java"), "to",
((Bindings) data).get("completions"), "javax.script.Bindings[]")) { data.get("completions"), "javax.script.Bindings[]")) {
completions.add(new QMLTernCompletion((String) completion.get("name"), completions.add(new QMLTernCompletion((String) completion.get("name"),
(String) completion.get("type"), (String) completion.get("origin"))); (String) completion.get("type"), (String) completion.get("origin")));
} }

View file

@ -2,8 +2,14 @@ function newTernServer(options) {
return new tern.Server(options); return new tern.Server(options);
} }
function requestCallback(obj) { function resolveDirectory(obj) {
return function(err, data) { return function (file, path) {
obj.callback(err, data); return obj.resolveDirectory(file, path);
} };
} }
function requestCallback(obj) {
return function (err, data) {
obj.callback(err, data);
};
}

View file

@ -57,7 +57,7 @@
dir = dir.substring(0, dir.lastIndexOf("/") + 1); dir = dir.substring(0, dir.lastIndexOf("/") + 1);
return dir; return dir;
} }
if (path.startsWith("./")) { if (path.substring(0, 2) === "./") {
path = file.directory + path.substring(2); path = file.directory + path.substring(2);
} }
if (path.substr(path.length - 1, 1) !== "/") { if (path.substr(path.length - 1, 1) !== "/") {

View file

@ -10,12 +10,18 @@
*******************************************************************************/ *******************************************************************************/
package org.eclipse.cdt.internal.qt.ui.editor; package org.eclipse.cdt.internal.qt.ui.editor;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.ui.Activator; import org.eclipse.cdt.internal.qt.ui.Activator;
import org.eclipse.cdt.internal.qt.ui.text.QMLSourceViewerConfiguration; import org.eclipse.cdt.internal.qt.ui.text.QMLSourceViewerConfiguration;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
@ -29,6 +35,7 @@ public class QMLEditor extends TextEditor {
private static final String BRACKET_MATCHING_PREFERENCE = "org.eclipse.cdt.qt.ui.qmlMatchingBrackets"; //$NON-NLS-1$ private static final String BRACKET_MATCHING_PREFERENCE = "org.eclipse.cdt.qt.ui.qmlMatchingBrackets"; //$NON-NLS-1$
private static final char[] BRACKETS = { '{', '}', '(', ')', '[', ']' }; private static final char[] BRACKETS = { '{', '}', '(', ')', '[', ']' };
private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
@Override @Override
protected void initializeEditor() { protected void initializeEditor() {
@ -36,6 +43,23 @@ public class QMLEditor extends TextEditor {
setSourceViewerConfiguration(new QMLSourceViewerConfiguration(this)); setSourceViewerConfiguration(new QMLSourceViewerConfiguration(this));
} }
@Override
public void doSave(IProgressMonitor progressMonitor) {
IFileEditorInput fileInput = (IFileEditorInput) getEditorInput();
String fileName = fileInput.getFile().getFullPath().toString().substring(1);
IDocument document = getSourceViewer().getDocument();
try {
analyzer.deleteFile(fileName);
analyzer.addFile(fileName, document.get());
} catch (NoSuchMethodException e) {
Activator.log(e);
} catch (ScriptException e) {
Activator.log(e);
}
super.doSave(progressMonitor);
}
@Override @Override
protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
super.configureSourceViewerDecorationSupport(support); super.configureSourceViewerDecorationSupport(support);

View file

@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.qt.ui.resources;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.script.ScriptException;
import org.eclipse.cdt.internal.qt.ui.Activator;
import org.eclipse.cdt.qt.core.QMLAnalyzer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
public class QMLTernFileUpdateJob extends Job {
private List<IResourceDelta> deltaList;
private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class);
public QMLTernFileUpdateJob(List<IResourceDelta> deltas) {
super("Add/Remove Files in Tern"); //$NON-NLS-1$
this.deltaList = deltas;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (IResourceDelta delta : deltaList) {
IResource resource = delta.getResource();
String fileName = resource.getFullPath().toString().substring(1);
if (resource instanceof IFile) {
IFile file = (IFile) resource;
try {
if ((delta.getKind() & IResourceDelta.ADDED) > 0) {
analyzer.addFile(fileName, readFileContents(file.getContents()));
} else if ((delta.getKind() & IResourceDelta.REMOVED) > 0) {
analyzer.deleteFile(fileName);
}
} catch (NoSuchMethodException e) {
Activator.log(e);
} catch (ScriptException e) {
Activator.log(e);
} catch (IOException e) {
Activator.log(e);
} catch (CoreException e) {
Activator.log(e);
}
}
}
return Status.OK_STATUS;
}
private String readFileContents(InputStream stream) throws IOException {
StringBuilder sb = new StringBuilder();
int read;
while ((read = stream.read()) != -1) {
sb.append((char) read);
}
return sb.toString();
}
}

View file

@ -38,6 +38,7 @@ public class QtResourceChangeListener implements IResourceChangeListener {
} }
final List<IResourceDelta> deltaList = new ArrayList<>(); final List<IResourceDelta> deltaList = new ArrayList<>();
final List<IResourceDelta> qmlDeltaList = new ArrayList<>();
IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() { IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
@Override @Override
@ -79,8 +80,13 @@ public class QtResourceChangeListener implements IResourceChangeListener {
return false; return false;
} }
// We don't care about resources that have simply been updated
if ((delta.getKind() & IResourceDelta.CHANGED) > 0) {
return false;
}
// We only care about added and removed resources at this point // We only care about added and removed resources at this point
if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.REMOVED)) == 0) { if ((delta.getKind() & IResourceDelta.ADDED | IResourceDelta.REMOVED) == 0) {
return false; return false;
} }
@ -91,6 +97,8 @@ public class QtResourceChangeListener implements IResourceChangeListener {
// Project. Add it to the list of deltas so we can update // Project. Add it to the list of deltas so we can update
// the project file later. // the project file later.
deltaList.add(delta); deltaList.add(delta);
} else if ("qml".equals(resource.getFileExtension())) { //$NON-NLS-1$
qmlDeltaList.add(delta);
} }
// Doesn't really matter since this line can only be reached if // Doesn't really matter since this line can only be reached if
@ -111,5 +119,9 @@ public class QtResourceChangeListener implements IResourceChangeListener {
if (!deltaList.isEmpty()) { if (!deltaList.isEmpty()) {
new QtProjectFileUpdateJob(deltaList).schedule(); new QtProjectFileUpdateJob(deltaList).schedule();
} }
// Schedule the job to update the tern server with added/deleted qml files
if (!qmlDeltaList.isEmpty()) {
new QMLTernFileUpdateJob(qmlDeltaList).schedule();
}
} }
} }

View file

@ -42,12 +42,11 @@ public class QMLContentAssistProcessor implements IContentAssistProcessor {
String prefix = lastWord(document, offset); String prefix = lastWord(document, offset);
// Save the file // Save the file
IFileEditorInput fileInput = (IFileEditorInput) editor.getEditorInput(); IFileEditorInput fileInput = (IFileEditorInput) editor.getEditorInput();
String fileName = fileInput.getFile().getName();// getLocation().toOSString().substring(1); String fileName = fileInput.getFile().getFullPath().toString().substring(1);// getLocation().toOSString().substring(1);
try { try {
String contents = document.get(); String contents = document.get();
analyzer.addFile(fileName, contents); Collection<QMLTernCompletion> completions = analyzer.getCompletions(fileName, contents, offset);
Collection<QMLTernCompletion> completions = analyzer.getCompletions(fileName, offset);
if (!completions.isEmpty()) { if (!completions.isEmpty()) {
ICompletionProposal[] proposals = new ICompletionProposal[completions.size()]; ICompletionProposal[] proposals = new ICompletionProposal[completions.size()];
int i = 0; int i = 0;
@ -64,8 +63,7 @@ public class QMLContentAssistProcessor implements IContentAssistProcessor {
return proposals; return proposals;
} }
} catch (NoSuchMethodException | ScriptException e) { } catch (NoSuchMethodException | ScriptException e) {
// TODO Auto-generated catch block Activator.log(e);
e.printStackTrace();
} }
return NO_COMPLETIONS; return NO_COMPLETIONS;
} }