From 42e3859b6810003568659a826691da07cdfb0eed Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Fri, 4 Dec 2015 17:08:10 -0500 Subject: [PATCH] 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 --- .../cdt/qt/core/tests/NashornTests.java | 4 +- .../org/eclipse/cdt/qt/core/QMLAnalyzer.java | 62 ++++++++++++++- .../tern-qml/qml-nsh.js | 14 +++- qt/org.eclipse.cdt.qt.core/tern-qml/qml.js | 2 +- .../cdt/internal/qt/ui/editor/QMLEditor.java | 24 ++++++ .../qt/ui/resources/QMLTernFileUpdateJob.java | 76 +++++++++++++++++++ .../resources/QtResourceChangeListener.java | 14 +++- .../qt/ui/text/QMLContentAssistProcessor.java | 8 +- 8 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java diff --git a/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java b/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java index bf41bc0d790..53e61490b2a 100644 --- a/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java +++ b/qt/org.eclipse.cdt.qt.core.tests/src/org/eclipse/cdt/qt/core/tests/NashornTests.java @@ -28,9 +28,7 @@ public class NashornTests { int pos = code.indexOf('|'); code = code.substring(0, pos) + code.substring(pos + 1); - analyzer.addFile("test1.qml", code); - - Collection QMLTernCompletions = analyzer.getCompletions("test1.qml", pos); + Collection QMLTernCompletions = analyzer.getCompletions("test1.qml", code, pos); Map set = new HashMap<>(); Set unexpected = new HashSet<>(); diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java index b28a06dabe2..583b3abca23 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QMLAnalyzer.java @@ -12,6 +12,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -24,6 +25,13 @@ import javax.script.ScriptEngineManager; import javax.script.ScriptException; 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") public class QMLAnalyzer { @@ -65,19 +73,51 @@ public class QMLAnalyzer { invoke.invokeMethod(defs, "push", engine.get("ecma5defs")); 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) { tern = invoke.invokeFunction("newTernServer", options); notifyAll(); } } + @FunctionalInterface + public interface ResolveDirectory { + public String resolveDirectory(Bindings file, String path); + } + private Object load(String file) throws ScriptException, IOException { URL scriptURL = Activator.getDefault().getBundle().getEntry(file); if (scriptURL == null) { throw new FileNotFoundException(file); } 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() { @@ -93,8 +133,9 @@ public class QMLAnalyzer { } } + @FunctionalInterface 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 { @@ -102,9 +143,21 @@ public class QMLAnalyzer { invoke.invokeMethod(tern, "addFile", fileName, code); } - public Collection getCompletions(String fileName, int pos) + public void deleteFile(String fileName) throws NoSuchMethodException, ScriptException { + waitUntilLoaded(); + invoke.invokeMethod(tern, "delFile", fileName); + } + + public Collection getCompletions(String fileName, String text, int pos) throws NoSuchMethodException, ScriptException { 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(); query.put("type", "completions"); query.put("file", fileName); @@ -119,6 +172,7 @@ public class QMLAnalyzer { query.put("includeKeywords", true); query.put("guess", false); Bindings request = engine.createBindings(); + request.put("files", files); request.put("query", query); List completions = new ArrayList<>(); @@ -129,7 +183,7 @@ public class QMLAnalyzer { } else { try { 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"), (String) completion.get("type"), (String) completion.get("origin"))); } diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/qml-nsh.js b/qt/org.eclipse.cdt.qt.core/tern-qml/qml-nsh.js index f4f68fe6d5b..47e3bcbd64f 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/qml-nsh.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/qml-nsh.js @@ -2,8 +2,14 @@ function newTernServer(options) { return new tern.Server(options); } -function requestCallback(obj) { - return function(err, data) { - obj.callback(err, data); - } +function resolveDirectory(obj) { + return function (file, path) { + return obj.resolveDirectory(file, path); + }; } + +function requestCallback(obj) { + return function (err, data) { + obj.callback(err, data); + }; +} \ No newline at end of file diff --git a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js index 9e49cac257a..89ace08be83 100644 --- a/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js +++ b/qt/org.eclipse.cdt.qt.core/tern-qml/qml.js @@ -57,7 +57,7 @@ dir = dir.substring(0, dir.lastIndexOf("/") + 1); return dir; } - if (path.startsWith("./")) { + if (path.substring(0, 2) === "./") { path = file.directory + path.substring(2); } if (path.substr(path.length - 1, 1) !== "/") { diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java index 0ba241611cb..051fd256fd5 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java @@ -10,12 +10,18 @@ *******************************************************************************/ 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.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.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; import org.eclipse.jface.text.source.ICharacterPairMatcher; +import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.editors.text.TextEditor; 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 char[] BRACKETS = { '{', '}', '(', ')', '[', ']' }; + private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class); @Override protected void initializeEditor() { @@ -36,6 +43,23 @@ public class QMLEditor extends TextEditor { 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 protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { super.configureSourceViewerDecorationSupport(support); diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java new file mode 100644 index 00000000000..ad9644c9f2d --- /dev/null +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java @@ -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 deltaList; + private final QMLAnalyzer analyzer = Activator.getService(QMLAnalyzer.class); + + public QMLTernFileUpdateJob(List 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(); + } +} diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QtResourceChangeListener.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QtResourceChangeListener.java index 0752a871254..b234e67330a 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QtResourceChangeListener.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QtResourceChangeListener.java @@ -38,6 +38,7 @@ public class QtResourceChangeListener implements IResourceChangeListener { } final List deltaList = new ArrayList<>(); + final List qmlDeltaList = new ArrayList<>(); IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() { @Override @@ -79,8 +80,13 @@ public class QtResourceChangeListener implements IResourceChangeListener { 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 - if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.REMOVED)) == 0) { + if ((delta.getKind() & IResourceDelta.ADDED | IResourceDelta.REMOVED) == 0) { return false; } @@ -91,6 +97,8 @@ public class QtResourceChangeListener implements IResourceChangeListener { // Project. Add it to the list of deltas so we can update // the project file later. 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 @@ -111,5 +119,9 @@ public class QtResourceChangeListener implements IResourceChangeListener { if (!deltaList.isEmpty()) { new QtProjectFileUpdateJob(deltaList).schedule(); } + // Schedule the job to update the tern server with added/deleted qml files + if (!qmlDeltaList.isEmpty()) { + new QMLTernFileUpdateJob(qmlDeltaList).schedule(); + } } } diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/text/QMLContentAssistProcessor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/text/QMLContentAssistProcessor.java index 3de94e1a392..57d2906b816 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/text/QMLContentAssistProcessor.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/text/QMLContentAssistProcessor.java @@ -42,12 +42,11 @@ public class QMLContentAssistProcessor implements IContentAssistProcessor { String prefix = lastWord(document, offset); // Save the file 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 { String contents = document.get(); - analyzer.addFile(fileName, contents); - Collection completions = analyzer.getCompletions(fileName, offset); + Collection completions = analyzer.getCompletions(fileName, contents, offset); if (!completions.isEmpty()) { ICompletionProposal[] proposals = new ICompletionProposal[completions.size()]; int i = 0; @@ -64,8 +63,7 @@ public class QMLContentAssistProcessor implements IContentAssistProcessor { return proposals; } } catch (NoSuchMethodException | ScriptException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Activator.log(e); } return NO_COMPLETIONS; }