mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
REST API /file
IDEA-135828 Command-line option to bring WebStorm to front
This commit is contained in:
@@ -2,12 +2,12 @@ var gulp = require('gulp')
|
||||
var apidoc = require('gulp-apidoc')
|
||||
var path = require('path')
|
||||
|
||||
var sources = path.resolve("../../platform/")
|
||||
var sources = path.resolve("../../platform/built-in-server/src")
|
||||
|
||||
gulp.task('apidoc', function () {
|
||||
var destDir = (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + "/idea-rest-api";
|
||||
console.info("generating docs from " + sources + " to " + destDir)
|
||||
apidoc.exec({src: sources, dest: destDir, debug: true})
|
||||
console.info("Generating docs from " + sources + " to " + destDir)
|
||||
apidoc.exec({src: sources, dest: destDir})
|
||||
})
|
||||
|
||||
gulp.task('default', ['apidoc'])
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<root>
|
||||
<item name='org.junit.rules.TestWatcher void finished(org.junit.runner.Description) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.junit.rules.TestWatcher void starting(org.junit.runner.Description) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -21,5 +21,6 @@
|
||||
<orderEntry type="module" module-name="testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="built-in-server-api" exported="" />
|
||||
<orderEntry type="module" module-name="vcs-api" />
|
||||
<orderEntry type="library" scope="TEST" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -44,15 +44,15 @@ import java.util.List;
|
||||
* @apiName diff
|
||||
* @apiGroup Platform
|
||||
*
|
||||
* @apiParam (properties) {String} [fileType] The file type name of the contents (see <a href="https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/FileType.java">FileType.getName()</a>).
|
||||
* @apiParam {String} [fileType] The file type name of the contents (see <a href="https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/FileType.java">FileType.getName()</a>).
|
||||
* You can get registered file types using <a href="#api-Platform-about">/rest/about?registeredFileTypes</a> request.
|
||||
* @apiParam (properties) {String} [windowTitle=Diff Service] The title of the diff window.
|
||||
* @apiParam (properties) {Boolean} [focused=true] Whether to focus project window.
|
||||
* @apiParam {String} [windowTitle=Diff Service] The title of the diff window.
|
||||
* @apiParam {Boolean} [focused=true] Whether to focus project window.
|
||||
*
|
||||
* @apiParam (properties) {Object[]{2..}} contents The list of the contents to diff.
|
||||
* @apiParam (properties) {String} [contents.title] The title of the content.
|
||||
* @apiParam (properties) {String} [contents.fileType] The file type name of the content.
|
||||
* @apiParam (properties) {String} contents.content The data of the content.
|
||||
* @apiParam {Object[]{2..}} contents The list of the contents to diff.
|
||||
* @apiParam {String} [contents.title] The title of the content.
|
||||
* @apiParam {String} [contents.fileType] The file type name of the content.
|
||||
* @apiParam {String} contents.content The data of the content.
|
||||
*
|
||||
* @apiUse DiffRequestExample
|
||||
*/
|
||||
@@ -107,7 +107,7 @@ final class DiffHttpService extends RestService {
|
||||
return "Empty request";
|
||||
}
|
||||
|
||||
Project project = guessProject();
|
||||
Project project = getLastFocusedOrOpenedProject();
|
||||
if (project == null) {
|
||||
// Argument for @NotNull parameter 'project' of com/intellij/openapi/components/ServiceManager.getService must not be null
|
||||
project = ProjectManager.getInstance().getDefaultProject();
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.jetbrains.ide;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.StringUtilRt;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import com.intellij.openapi.vcs.VcsRoot;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.newvfs.ManagingFS;
|
||||
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
|
||||
import com.intellij.openapi.vfs.newvfs.RefreshSession;
|
||||
import com.intellij.ui.AppUIUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
|
||||
import org.jetbrains.concurrency.AsyncPromise;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @api {get} /file Open file
|
||||
* @apiName file
|
||||
* @apiGroup Platform
|
||||
*
|
||||
* @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
|
||||
* @apiParam {Integer} [line] The line number of the file (1-based).
|
||||
* @apiParam {Integer} [column] The column number of the file (1-based).
|
||||
* @apiParam {Boolean} [focused=true] Whether to focus project window.
|
||||
*
|
||||
* @apiExample {curl} Absolute path
|
||||
* curl http://localhost:63342/api/file//absolute/path/to/file.kt
|
||||
*
|
||||
* @apiExample {curl} Relative path
|
||||
* curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
|
||||
*
|
||||
* @apiExample {curl} With line and column
|
||||
* curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
|
||||
*
|
||||
* @apiExample {curl} Query parameters
|
||||
* curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
|
||||
*/
|
||||
class OpenFileHttpService extends RestService {
|
||||
private static final RuntimeException NOT_FOUND = Promise.createError("not found");
|
||||
private static final Pattern LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$");
|
||||
|
||||
private volatile long refreshSessionId = 0;
|
||||
private final ConcurrentLinkedQueue<OpenFileTask> requests = new ConcurrentLinkedQueue<OpenFileTask>();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getServiceName() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMethodSupported(@NotNull HttpMethod method) {
|
||||
return method == HttpMethod.GET || method == HttpMethod.POST;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
|
||||
final boolean keepAlive = HttpHeaders.isKeepAlive(request);
|
||||
final Channel channel = context.channel();
|
||||
|
||||
String file;
|
||||
int line;
|
||||
int column;
|
||||
|
||||
if (request.method() == HttpMethod.POST) {
|
||||
OpenFileRequest jsonRequest = new Gson().fromJson(createJsonReader(request), OpenFileRequest.class);
|
||||
file = jsonRequest.file;
|
||||
line = jsonRequest.line;
|
||||
column = jsonRequest.column;
|
||||
}
|
||||
else {
|
||||
file = StringUtil.nullize(getStringParameter("file", urlDecoder), true);
|
||||
line = getIntParameter("line", urlDecoder);
|
||||
column = getIntParameter("column", urlDecoder);
|
||||
}
|
||||
|
||||
|
||||
int prefixLength = 1 + PREFIX.length() + 1 + getServiceName().length() + 1;
|
||||
String path = urlDecoder.path();
|
||||
if (path.length() > prefixLength) {
|
||||
Matcher matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length());
|
||||
LOG.assertTrue(matcher.matches());
|
||||
if (file == null) {
|
||||
file = matcher.group(1).trim();
|
||||
}
|
||||
if (line == -1) {
|
||||
line = StringUtilRt.parseInt(matcher.group(2), 1);
|
||||
}
|
||||
if (column == -1) {
|
||||
column = StringUtilRt.parseInt(matcher.group(3), 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
openFile(file, line, column, getBooleanParameter("focused", urlDecoder))
|
||||
.done(new Consumer<Void>() {
|
||||
@Override
|
||||
public void consume(Void aVoid) {
|
||||
sendStatus(HttpResponseStatus.OK, keepAlive, channel);
|
||||
}
|
||||
})
|
||||
.rejected(new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void consume(Throwable throwable) {
|
||||
if (throwable == NOT_FOUND) {
|
||||
sendStatus(HttpResponseStatus.NOT_FOUND, keepAlive, channel);
|
||||
}
|
||||
else {
|
||||
// todo send error
|
||||
sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel);
|
||||
LOG.error(throwable);
|
||||
}
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void navigate(@Nullable Project project, @NotNull VirtualFile file, int line, int column, boolean focused) {
|
||||
if (project == null) {
|
||||
project = getLastFocusedOrOpenedProject();
|
||||
if (project == null) {
|
||||
project = ProjectManager.getInstance().getDefaultProject();
|
||||
}
|
||||
}
|
||||
|
||||
new OpenFileDescriptor(project, file, line, column).navigate(true);
|
||||
if (focused) {
|
||||
com.intellij.ide.impl.ProjectUtil.focusProjectWindow(project, true);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Promise<Void> openFile(@NotNull String path, int line, final int column, final boolean focused) {
|
||||
path = FileUtil.expandUserHome(path);
|
||||
final File file = new File(FileUtil.toSystemDependentName(path));
|
||||
if (file.isAbsolute()) {
|
||||
return openAbsolutePath(file, line, column, focused);
|
||||
}
|
||||
|
||||
// we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
|
||||
RefreshQueue queue = RefreshQueue.getInstance();
|
||||
queue.cancelSession(refreshSessionId);
|
||||
OpenFileTask request = new OpenFileTask(FileUtil.toCanonicalPath(FileUtil.toSystemIndependentName(path), '/'), line, column);
|
||||
requests.offer(request);
|
||||
RefreshSession session = queue.createSession(true, true, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OpenFileTask request;
|
||||
while ((request = requests.poll()) != null) {
|
||||
try {
|
||||
if (openRelativePath(request.path, request.line, request.column, focused)) {
|
||||
request.promise.setResult(null);
|
||||
}
|
||||
else {
|
||||
request.promise.setError(NOT_FOUND);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
request.promise.setError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
|
||||
session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
|
||||
refreshSessionId = session.getId();
|
||||
session.launch();
|
||||
return request.promise;
|
||||
}
|
||||
|
||||
// path must be normalized
|
||||
private static boolean openRelativePath(@NotNull final String path, final int line, final int column, final boolean focused) {
|
||||
VirtualFile virtualFile = null;
|
||||
Project project = null;
|
||||
|
||||
Project[] projects = ProjectManager.getInstance().getOpenProjects();
|
||||
for (Project openedProject : projects) {
|
||||
VirtualFile openedProjectBaseDir = openedProject.getBaseDir();
|
||||
if (openedProjectBaseDir != null) {
|
||||
virtualFile = openedProjectBaseDir.findFileByRelativePath(path);
|
||||
}
|
||||
|
||||
if (virtualFile == null) {
|
||||
virtualFile = WebServerPathToFileManager.getInstance(openedProject).get(path);
|
||||
}
|
||||
if (virtualFile != null) {
|
||||
project = openedProject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (virtualFile == null) {
|
||||
for (Project openedProject : projects) {
|
||||
for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(openedProject).getAllVcsRoots()) {
|
||||
VirtualFile root = vcsRoot.getPath();
|
||||
if (root != null) {
|
||||
virtualFile = root.findFileByRelativePath(path);
|
||||
if (virtualFile != null) {
|
||||
project = openedProject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (virtualFile == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Project finalProject = project;
|
||||
final VirtualFile finalVirtualFile = virtualFile;
|
||||
AppUIUtil.invokeLaterIfProjectAlive(project, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
navigate(finalProject, finalVirtualFile, line, column, focused);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Promise<Void> openAbsolutePath(@NotNull final File file, final int line, final int column, final boolean focused) {
|
||||
if (!file.exists()) {
|
||||
return Promise.reject(NOT_FOUND);
|
||||
}
|
||||
|
||||
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
VirtualFile virtualFile;
|
||||
AccessToken token = WriteAction.start();
|
||||
try {
|
||||
virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
|
||||
}
|
||||
finally {
|
||||
token.finish();
|
||||
}
|
||||
|
||||
if (virtualFile == null) {
|
||||
promise.setError(NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, line, column, focused);
|
||||
promise.setResult(null);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
promise.setError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
private static final class OpenFileTask {
|
||||
final String path;
|
||||
final int line;
|
||||
final int column;
|
||||
|
||||
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
|
||||
|
||||
OpenFileTask(@NotNull String path, int line, int column) {
|
||||
this.path = path;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OpenFileRequest {
|
||||
public String file;
|
||||
public int line;
|
||||
public int column;
|
||||
|
||||
public boolean focused;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,30 +15,11 @@
|
||||
*/
|
||||
package org.jetbrains.ide;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorProvider;
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectLocator;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import com.intellij.openapi.vcs.VcsRoot;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author mike
|
||||
*/
|
||||
public class OpenFileXmlRpcHandler {
|
||||
class OpenFileXmlRpcHandler {
|
||||
private static final Logger LOG = Logger.getInstance(OpenFileXmlRpcHandler.class);
|
||||
|
||||
// XML-RPC interface method - keep the signature intact
|
||||
@@ -55,75 +36,7 @@ public class OpenFileXmlRpcHandler {
|
||||
return doOpen(path, line, column);
|
||||
}
|
||||
|
||||
private static boolean doOpen(final String path, final int line, final int column) {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Pair<VirtualFile, Project> data = new File(path).isAbsolute() ? findByAbsolutePath(path) : findByRelativePath(path);
|
||||
if (data == null) return;
|
||||
|
||||
FileEditorProvider[] providers = FileEditorProviderManager.getInstance().getProviders(data.second, data.first);
|
||||
if (providers.length == 0) return;
|
||||
|
||||
OpenFileDescriptor descriptor = new OpenFileDescriptor(data.second, data.first, line, column);
|
||||
FileEditorManager.getInstance(data.second).openTextEditor(descriptor, true);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
private static boolean doOpen(@NotNull String path, int line, int column) {
|
||||
return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(path, line, column, false).getState() != Promise.State.REJECTED;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<VirtualFile, Project> findByAbsolutePath(String path) {
|
||||
File file = new File(FileUtil.toSystemDependentName(path));
|
||||
if (file.exists()) {
|
||||
VirtualFile vFile = findVirtualFile(file);
|
||||
if (vFile != null) {
|
||||
Project project = ProjectLocator.getInstance().guessProjectForFile(vFile);
|
||||
if (project != null) {
|
||||
return Pair.create(vFile, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<VirtualFile, Project> findByRelativePath(String path) {
|
||||
Project[] projects = ProjectManager.getInstance().getOpenProjects();
|
||||
String localPath = FileUtil.toSystemDependentName(path);
|
||||
|
||||
for (Project project : projects) {
|
||||
File file = new File(project.getBasePath(), localPath);
|
||||
if (file.exists()) {
|
||||
VirtualFile vFile = findVirtualFile(file);
|
||||
return vFile != null ? Pair.create(vFile, project) : null;
|
||||
}
|
||||
}
|
||||
|
||||
for (Project project : projects) {
|
||||
for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(project).getAllVcsRoots()) {
|
||||
VirtualFile root = vcsRoot.getPath();
|
||||
if (root != null) {
|
||||
File file = new File(FileUtil.toSystemDependentName(root.getPath()), localPath);
|
||||
if (file.exists()) {
|
||||
VirtualFile vFile = findVirtualFile(file);
|
||||
return vFile != null ? Pair.create(vFile, project) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static VirtualFile findVirtualFile(final File file) {
|
||||
return ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
|
||||
@Override
|
||||
public VirtualFile compute() {
|
||||
return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,8 @@ import java.io.IOException;
|
||||
* @apiGroup Platform
|
||||
* @apiDescription Checkout a repository from source control and then open an IDEA project from it.
|
||||
*
|
||||
* @apiParam (properties) {Object} vcs The map of the VCS.
|
||||
* @apiParam (properties) {String} project The path to project to be opened.
|
||||
* @apiParam {Object} vcs The map of the VCS.
|
||||
* @apiParam {String} project The path to project to be opened.
|
||||
*
|
||||
* @apiUse OpenProjectSetRequestExample
|
||||
* @apiUse OpenProjectSetRequestExampleMulti
|
||||
|
||||
@@ -22,6 +22,8 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.StringUtilRt;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
@@ -29,6 +31,7 @@ import com.intellij.util.ExceptionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -50,7 +53,7 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class RestService extends HttpRequestHandler {
|
||||
protected static final Logger LOG = Logger.getInstance(RestService.class);
|
||||
private static final String PREFIX = "api";
|
||||
public static final String PREFIX = "api";
|
||||
|
||||
@Override
|
||||
public final boolean isSupported(@NotNull FullHttpRequest request) {
|
||||
@@ -142,7 +145,7 @@ public abstract class RestService extends HttpRequestHandler {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static Project guessProject() {
|
||||
protected static Project getLastFocusedOrOpenedProject() {
|
||||
IdeFrame lastFocusedFrame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
|
||||
Project project = lastFocusedFrame == null ? null : lastFocusedFrame.getProject();
|
||||
if (project == null) {
|
||||
@@ -153,7 +156,18 @@ public abstract class RestService extends HttpRequestHandler {
|
||||
}
|
||||
|
||||
protected static void sendOk(@NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
|
||||
Responses.send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK), context.channel(), request);
|
||||
sendStatus(HttpResponseStatus.OK, HttpHeaders.isKeepAlive(request), context.channel());
|
||||
}
|
||||
|
||||
protected static void sendStatus(@NotNull HttpResponseStatus status, boolean keepAlive, @NotNull Channel channel) {
|
||||
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
|
||||
HttpHeaders.setContentLength(response, 0);
|
||||
Responses.addCommonHeaders(response);
|
||||
Responses.addNoCache(response);
|
||||
if (keepAlive) {
|
||||
HttpHeaders.setKeepAlive(response, true);
|
||||
}
|
||||
Responses.send(response, channel, !keepAlive);
|
||||
}
|
||||
|
||||
protected static void send(@NotNull BufferExposingByteArrayOutputStream byteOut, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
|
||||
@@ -162,6 +176,15 @@ public abstract class RestService extends HttpRequestHandler {
|
||||
Responses.send(response, context.channel(), request);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static String getStringParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
|
||||
return ContainerUtil.getLastItem(urlDecoder.parameters().get(name));
|
||||
}
|
||||
|
||||
protected static int getIntParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
|
||||
return StringUtilRt.parseInt(StringUtil.nullize(ContainerUtil.getLastItem(urlDecoder.parameters().get(name)), true), -1);
|
||||
}
|
||||
|
||||
protected static boolean getBooleanParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
|
||||
List<String> values = urlDecoder.parameters().get(name);
|
||||
if (ContainerUtil.isEmpty(values)) {
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class Responses {
|
||||
send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset)), channel, request);
|
||||
}
|
||||
|
||||
private static void send(@NotNull HttpResponse response, @NotNull Channel channel, boolean close) {
|
||||
public static void send(@NotNull HttpResponse response, @NotNull Channel channel, boolean close) {
|
||||
if (!channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import com.intellij.testFramework.PlatformTestCase
|
||||
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
|
||||
import org.junit.rules.ExternalResource
|
||||
import javax.swing.SwingUtilities
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
|
||||
fun invokeAndWaitIfNeed(runnable: () -> Unit) {
|
||||
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
|
||||
}
|
||||
|
||||
inline fun runWriteAction(runnable: () -> Unit) {
|
||||
val token = WriteAction.start()
|
||||
try {
|
||||
runnable()
|
||||
}
|
||||
finally {
|
||||
token.finish()
|
||||
}
|
||||
}
|
||||
|
||||
public class FixtureRule() : ExternalResource() {
|
||||
val projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder().getFixture()
|
||||
|
||||
override fun before() {
|
||||
PlatformTestCase.initPlatformLangPrefix()
|
||||
|
||||
invokeAndWaitIfNeed { projectFixture.setUp() }
|
||||
}
|
||||
|
||||
override fun after() {
|
||||
invokeAndWaitIfNeed { projectFixture.tearDown() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.jetbrains.ide.TestManager.TestDescriptor
|
||||
import org.junit.Assert.assertThat
|
||||
import java.net.URL
|
||||
import java.net.HttpURLConnection
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.io.OutputStreamWriter
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import java.io.BufferedOutputStream
|
||||
|
||||
public class RestApiTest {
|
||||
private val fixtureManager = FixtureRule()
|
||||
private val manager = TestManager(fixtureManager.projectFixture)
|
||||
|
||||
private val _chain = RuleChain
|
||||
.outerRule(fixtureManager)
|
||||
.around(manager)
|
||||
|
||||
Rule
|
||||
public fun getChain(): RuleChain = _chain
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "", status = 400)
|
||||
public fun fileEmptyRequest() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "foo.txt", relativeToProject = true, status = 200)
|
||||
public fun relativeToProject() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "foo.txt", relativeToProject = true, line = 1, status = 200)
|
||||
public fun relativeToProjectWithLine() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "foo.txt", relativeToProject = true, line = 1, column = 13, status = 200)
|
||||
public fun relativeToProjectWithLineAndColumn() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "fileInExcludedDir.txt", excluded = true, status = 200)
|
||||
public fun inExcludedDir() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "bar/42/foo.txt", doNotCreate = true, status = 404)
|
||||
public fun relativeNonExistent() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "_tmp_", doNotCreate = true, status = 404)
|
||||
public fun absoluteNonExistent() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
Test
|
||||
TestDescriptor(filePath = "_tmp_", status = 200)
|
||||
public fun absolute() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
private fun doTest() {
|
||||
val serviceUrl = "http://localhost:" + BuiltInServerManager.getInstance().getPort() + "/api/file"
|
||||
var url = serviceUrl + (if (manager.filePath == null) "" else ("/" + manager.filePath))
|
||||
val line = manager.annotation?.line ?: -1
|
||||
if (line != -1) {
|
||||
url += ":$line"
|
||||
}
|
||||
val column = manager.annotation?.column ?: -1
|
||||
if (column != -1) {
|
||||
url += ":$column"
|
||||
}
|
||||
|
||||
var connection = URL(url).openConnection() as HttpURLConnection
|
||||
val expectedStatus = HttpResponseStatus.valueOf(manager.annotation?.status ?: 200)
|
||||
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
|
||||
|
||||
connection = URL("$serviceUrl?file=${manager.filePath ?: ""}&line=$line&column=$column").openConnection() as HttpURLConnection
|
||||
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
|
||||
|
||||
connection = URL("$serviceUrl").openConnection() as HttpURLConnection
|
||||
connection.setRequestMethod("POST")
|
||||
connection.setDoOutput(true)
|
||||
val writer = JsonWriter(OutputStreamWriter(BufferedOutputStream(connection.getOutputStream()), CharsetToolkit.UTF8_CHARSET))
|
||||
writer.beginObject()
|
||||
writer.name("file").value(manager.filePath)
|
||||
writer.name("line").value(line)
|
||||
writer.name("column").value(column)
|
||||
writer.endObject()
|
||||
writer.close()
|
||||
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
|
||||
import java.lang.annotation.ElementType
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import java.lang.annotation.Target
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import java.io.File
|
||||
|
||||
class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
|
||||
class object {
|
||||
val EXCLUDED_DIR_NAME = "excludedDir"
|
||||
}
|
||||
|
||||
public var annotation: TestDescriptor? = null
|
||||
|
||||
public var filePath: String? = null
|
||||
private var fileToDelete: VirtualFile? = null
|
||||
|
||||
private var ioFileToDelete: File? = null
|
||||
|
||||
Retention(RetentionPolicy.RUNTIME)
|
||||
Target(ElementType.METHOD)
|
||||
annotation public class TestDescriptor(public val filePath: String,
|
||||
public val line: Int = -1,
|
||||
public val column: Int = -1,
|
||||
public val relativeToProject: Boolean = false,
|
||||
public val excluded: Boolean = false,
|
||||
public val doNotCreate: Boolean = false,
|
||||
public val status: Int = 200)
|
||||
|
||||
override fun starting(description: Description) {
|
||||
annotation = description.getAnnotation<TestDescriptor>(javaClass<TestDescriptor>())
|
||||
if (annotation == null) {
|
||||
return
|
||||
}
|
||||
|
||||
filePath = StringUtil.nullize(annotation!!.filePath)
|
||||
if (filePath == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (filePath!! == "_tmp_") {
|
||||
if (annotation!!.doNotCreate) {
|
||||
filePath = FileUtilRt.generateRandomTemporaryPath().getPath()
|
||||
}
|
||||
else {
|
||||
ioFileToDelete = FileUtilRt.createTempFile("testFile", ".txt")
|
||||
filePath = ioFileToDelete!!.getPath()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (annotation!!.doNotCreate) {
|
||||
return
|
||||
}
|
||||
|
||||
invokeAndWaitIfNeed {
|
||||
val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath)
|
||||
if (annotation!!.relativeToProject) {
|
||||
val root = projectFixture.getProject().getBaseDir()
|
||||
runWriteAction {
|
||||
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (annotation!!.excluded) {
|
||||
ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model ->
|
||||
val contentEntry = model.getContentEntries()[0]
|
||||
val contentRoot = contentEntry.getFile()!!
|
||||
runWriteAction {
|
||||
contentRoot.findChild(EXCLUDED_DIR_NAME)?.delete(this@TestManager)
|
||||
fileToDelete = contentRoot.createChildDirectory(this@TestManager, EXCLUDED_DIR_NAME)
|
||||
fileToDelete!!.createChildData(this@TestManager, normalizedFilePath)
|
||||
}
|
||||
contentEntry.addExcludeFolder(fileToDelete!!)
|
||||
}
|
||||
|
||||
filePath = "$EXCLUDED_DIR_NAME/$filePath"
|
||||
}
|
||||
else {
|
||||
val root = ModuleRootManager.getInstance(projectFixture.getModule()).getSourceRoots()[0]
|
||||
runWriteAction {
|
||||
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun finished(description: Description?) {
|
||||
if (annotation!!.excluded) {
|
||||
ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model -> model.getContentEntries()[0].removeExcludeFolder(EXCLUDED_DIR_NAME) }
|
||||
}
|
||||
|
||||
if (fileToDelete != null) {
|
||||
invokeAndWaitIfNeed { runWriteAction { fileToDelete?.delete(this@TestManager) } }
|
||||
fileToDelete = null
|
||||
}
|
||||
|
||||
if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete)) {
|
||||
ioFileToDelete!!.deleteOnExit()
|
||||
}
|
||||
|
||||
annotation = null
|
||||
filePath = null
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.XmlRpcServerImpl$XmlRpcRequestHandler"/>
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.ProjectSetRequestHandler"/>
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.DiffHttpService"/>
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.OpenFileHttpService"/>
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.AboutHttpService"/>
|
||||
<httpRequestHandler implementation="org.jetbrains.builtInWebServer.BuiltInWebServer"/>
|
||||
</extensions>
|
||||
|
||||
Reference in New Issue
Block a user