diff --git a/build/build.iml b/build/build.iml
index 7486cf38b75d..56fe67fc3588 100644
--- a/build/build.iml
+++ b/build/build.iml
@@ -5,6 +5,7 @@
+
@@ -35,5 +36,4 @@
-
-
+
\ No newline at end of file
diff --git a/build/restApiDocGenerator/.gitignore b/build/restApiDocGenerator/.gitignore
new file mode 100644
index 000000000000..40b878db5b1c
--- /dev/null
+++ b/build/restApiDocGenerator/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/build/restApiDocGenerator/apidoc.json b/build/restApiDocGenerator/apidoc.json
new file mode 100644
index 000000000000..bf3fd87a4282
--- /dev/null
+++ b/build/restApiDocGenerator/apidoc.json
@@ -0,0 +1,5 @@
+{
+ "name": "IntelliJ Platform REST API",
+ "version": "140.2110",
+ "url" : "http://localhost:63342/rest"
+}
\ No newline at end of file
diff --git a/build/restApiDocGenerator/gulpfile.js b/build/restApiDocGenerator/gulpfile.js
new file mode 100644
index 000000000000..e583e04c7c23
--- /dev/null
+++ b/build/restApiDocGenerator/gulpfile.js
@@ -0,0 +1,15 @@
+var gulp = require('gulp')
+var apidoc = require('gulp-apidoc')
+var path = require('path')
+
+var sources = path.normalize("../../platform/platform-impl/src/org/jetbrains/ide")
+
+gulp.task('apidoc', function () {
+ apidoc.exec({src: sources, dest: (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + "/idea-rest-api"})
+})
+
+gulp.task('default', ['apidoc'])
+
+gulp.task('watch', function() {
+ gulp.watch(sources + "/**/*.{clj,coffee,cs,dart,erl,go,java,js,php,py,rb,ts,pm}", ['apidoc'])
+})
\ No newline at end of file
diff --git a/build/restApiDocGenerator/package.json b/build/restApiDocGenerator/package.json
new file mode 100644
index 000000000000..eb04a6f6572f
--- /dev/null
+++ b/build/restApiDocGenerator/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "restApiDocGenerator",
+ "devDependencies": {
+ "gulp": "^3.8.10",
+ "gulp-apidoc": "^0.1.2"
+ }
+}
diff --git a/platform/core-impl/src/com/intellij/psi/stubs/StubTree.java b/platform/core-impl/src/com/intellij/psi/stubs/StubTree.java
index 16488845acff..fa5bc5b3de91 100644
--- a/platform/core-impl/src/com/intellij/psi/stubs/StubTree.java
+++ b/platform/core-impl/src/com/intellij/psi/stubs/StubTree.java
@@ -62,7 +62,10 @@ public class StubTree extends ObjectStubTree> {
@NotNull
@Override
public List> getPlainListFromAllRoots() {
- return ContainerUtil.concat(getRoot().getStubRoots(), new Function>>() {
+ final PsiFileStub[] roots = getRoot().getStubRoots();
+ if (roots.length == 1) return super.getPlainListFromAllRoots();
+
+ return ContainerUtil.concat(roots, new Function>>() {
@Override
public Collection extends StubElement>> fun(PsiFileStub stub) {
final ObjectStubTree existingTree = stub.getUserData(STUB_TO_TREE_REFERENCE);
diff --git a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java
index 1a926e66f333..4cf61591605c 100644
--- a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java
+++ b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java
@@ -219,14 +219,16 @@ public class JBScrollPane extends JScrollPane {
if (hsb != null) container.setComponentZOrder(hsb, 0);
if (extendViewportUnderVScrollbar) {
+ int x2 = Math.max(vsb.getX() + vsb.getWidth(), viewportBounds.x + viewportBounds.width);
viewportBounds.x = Math.min(viewportBounds.x, vsb.getX());
- viewportBounds.width = vsb.getX() + vsb.getWidth() - viewportBounds.x;
+ viewportBounds.width = x2 - viewportBounds.x;
}
if (extendViewportUnderHScrollbar) {
+ int y2 = Math.max(hsb.getY() + hsb.getHeight(), viewportBounds.y + viewportBounds.height);
viewportBounds.y = Math.min(viewportBounds.y, hsb.getY());
- viewportBounds.height = hsb.getY() + hsb.getHeight() - viewportBounds.y;
+ viewportBounds.height = y2 - viewportBounds.y;
}
-
+
if (extendViewportUnderVScrollbar) {
if (hsb != null) {
Rectangle scrollbarBounds = hsb.getBounds();
diff --git a/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.coffee b/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.coffee
new file mode 100644
index 000000000000..1c7a257611bf
--- /dev/null
+++ b/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.coffee
@@ -0,0 +1,43 @@
+###
+ @apiDefine SuccessExample
+
+ @apiSuccessExample {json} Success-Response:
+{
+ "name": "WebStorm 140.SNAPSHOT",
+ "productName": "WebStorm",
+ "baselineVersion": 140
+}
+###
+
+###
+ @apiDefine SuccessExampleWithRegisteredFileTypes
+
+ @apiSuccessExample {json} Success-Response (with registeredFileTypes):
+{
+ "name": "WebStorm 140.SNAPSHOT",
+ "productName": "WebStorm",
+ "baselineVersion": 140,
+ "registeredFileTypes": [
+ {
+ "name": "HTML",
+ "description": "HTML files",
+ "isBinary": false
+ },
+ {
+ "name": "XHTML",
+ "description": "XHTML files",
+ "isBinary": false
+ },
+ {
+ "name": "DTD",
+ "description": "XML Document Type Definition",
+ "isBinary": false
+ },
+ {
+ "name": "XML",
+ "description": "XML files",
+ "isBinary": false
+ }
+ ]
+}
+###
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.java b/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.java
new file mode 100644
index 000000000000..a2ad88973999
--- /dev/null
+++ b/platform/platform-impl/src/org/jetbrains/ide/AboutHttpService.java
@@ -0,0 +1,115 @@
+/*
+ * 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.stream.JsonWriter;
+import com.intellij.openapi.application.ApplicationInfo;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.application.ex.ApplicationInfoEx;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeRegistry;
+import com.intellij.openapi.util.BuildNumber;
+import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
+import com.intellij.openapi.vfs.CharsetToolkit;
+import com.intellij.util.PlatformUtils;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.Responses;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ * @api {get} /about The application info
+ * @apiName about
+ * @apiGroup Platform
+ *
+ * @apiParam {Boolean} [registeredFileTypes=false] Whether to include the list of registered file types.
+ *
+ * @apiSuccess {String} name The full application name.
+ * @apiSuccess {String} productName The product name.
+ * @apiSuccess {String} baselineVersion The baseline version.
+ * @apiSuccess {String} [buildNumber] The build number.
+ *
+ * @apiSuccess {Object[]} registeredFileTypes The list of registered file types.
+ * @apiSuccess {String} registeredFileTypes.name The name of file type.
+ * @apiSuccess {String} registeredFileTypes.description The user-readable description of the file type.
+ * @apiSuccess {Boolean} registeredFileTypes.isBinary Whether files of the specified type contain binary data.
+ *
+ * * @apiExample Request-Example:
+ * /rest/about?registeredFileTypes
+ *
+ * @apiUse SuccessExample
+ * @apiUse SuccessExampleWithRegisteredFileTypes
+ */
+class AboutHttpService extends RestService {
+ @NotNull
+ @Override
+ protected String getServiceName() {
+ return "about";
+ }
+
+ @Override
+ protected boolean isMethodSupported(@NotNull HttpMethod method) {
+ return method == HttpMethod.GET;
+ }
+
+ @Nullable
+ @Override
+ public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
+ BuildNumber build = ApplicationInfo.getInstance().getBuild();
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream();
+ JsonWriter writer = new JsonWriter(new OutputStreamWriter(byteOut, CharsetToolkit.UTF8_CHARSET));
+ writer.beginObject();
+
+ String appName = ApplicationInfoEx.getInstanceEx().getFullApplicationName();
+ if (!PlatformUtils.isIdeaUltimate()) {
+ String productName = ApplicationNamesInfo.getInstance().getProductName();
+ appName = appName.replace(productName + " (" + productName + ")", productName);
+ if (appName.startsWith("JetBrains ")) {
+ appName = appName.substring("JetBrains ".length());
+ }
+ }
+
+ writer.name("name").value(appName);
+ writer.name("productName").value(ApplicationNamesInfo.getInstance().getProductName());
+ writer.name("baselineVersion").value(build.getBaselineVersion());
+ if (build.getBuildNumber() != Integer.MAX_VALUE) {
+ writer.name("buildNumber").value(build.getBuildNumber());
+ }
+
+ if (getBooleanParameter("registeredFileTypes", urlDecoder)) {
+ writer.name("registeredFileTypes").beginArray();
+ for (FileType fileType : FileTypeRegistry.getInstance().getRegisteredFileTypes()) {
+ writer.beginObject();
+ writer.name("name").value(fileType.getName());
+ writer.name("description").value(fileType.getDescription());
+ writer.name("isBinary").value(fileType.isBinary());
+ writer.endObject();
+ }
+ writer.endArray();
+ }
+
+ writer.endObject();
+ writer.close();
+ Responses.send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(byteOut.getInternalBuffer(), 0, byteOut.size())), context.channel(), request);
+ return null;
+ }
+}
diff --git a/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.coffee b/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.coffee
new file mode 100644
index 000000000000..cbd9111c0f1d
--- /dev/null
+++ b/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.coffee
@@ -0,0 +1,18 @@
+###
+ @apiDefine DiffRequestExample
+
+ @apiExample {json} Request-Example:
+{
+ "fileType": "JSON",
+ "contents": [
+ {
+ "title": "Actual",
+ "content": "foo"
+ },
+ {
+ "title": "Expected",
+ "content": "bar"
+ }
+ ]
+}
+###
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.java b/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.java
index e91418e6103e..73d64bd4c694 100644
--- a/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/DiffHttpService.java
@@ -38,34 +38,57 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+/**
+ * @api {post} /diff The differences between contents
+ * @apiName diff
+ * @apiGroup Platform
+ *
+ * @apiParam (properties) {String} [fileType] The file type name of the contents (see FileType.getName()).
+ * You can get registered file types using /rest/about?registeredFileTypes request.
+ * @apiParam (properties) {String} [windowTitle=Diff Service] The title of the diff window.
+ * @apiParam (properties) {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.
+ *
+ * @apiUse DiffRequestExample
+ */
final class DiffHttpService extends RestService {
- @Override
- protected boolean isMethodSupported(@NotNull HttpMethod method) {
- return method == HttpMethod.POST;
- }
-
@NotNull
@Override
protected String getServiceName() {
return "diff";
}
+ @Override
+ protected boolean isMethodSupported(@NotNull HttpMethod method) {
+ return method == HttpMethod.POST;
+ }
+
@Override
@Nullable
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
final List contents = new ArrayList();
final List titles = new ArrayList();
-
+ boolean focused = true;
+ String windowTitle = null;
JsonReader reader = createJsonReader(request);
if (reader.hasNext()) {
String fileType = null;
- boolean reformat;
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
- if (name.equals("fileTypeName")) {
+ if (name.equals("fileType")) {
fileType = reader.nextString();
}
+ else if (name.equals("focused")) {
+ focused = reader.nextBoolean();
+ }
+ else if (name.equals("windowTitle")) {
+ windowTitle = StringUtil.nullize(reader.nextString(), true);
+ }
else if (name.equals("contents")) {
String error = readContent(reader, contents, titles, fileType);
if (error != null) {
@@ -89,6 +112,8 @@ final class DiffHttpService extends RestService {
return "No opened project, please open any project";
}
+ final boolean finalFocused = focused;
+ final String finalWindowTitle = windowTitle;
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
@@ -106,21 +131,23 @@ final class DiffHttpService extends RestService {
@Override
public String getWindowTitle() {
- return "Diff Service";
+ return StringUtil.notNullize(finalWindowTitle, "Diff Service");
}
});
- ProjectUtil.focusProjectWindow(project, true);
+ if (finalFocused) {
+ ProjectUtil.focusProjectWindow(project, true);
+ }
}
}, project.getDisposed());
return null;
}
@Nullable
- private static String readContent(@NotNull JsonReader reader, @NotNull List contents, @NotNull List titles, @Nullable String defaultFileTypeName) throws IOException {
+ private static String readContent(@NotNull JsonReader reader, @NotNull List contents, @NotNull List titles, @Nullable String defaultfileType) throws IOException {
FileTypeRegistry fileTypeRegistry = FileTypeRegistry.getInstance();
- FileType defaultFileType = defaultFileTypeName == null ? null : fileTypeRegistry.findFileTypeByName(defaultFileTypeName);
+ FileType defaultFileType = defaultfileType == null ? null : fileTypeRegistry.findFileTypeByName(defaultfileType);
reader.beginArray();
while (reader.hasNext()) {
String title = null;
@@ -133,7 +160,7 @@ final class DiffHttpService extends RestService {
if (name.equals("title")) {
title = reader.nextString();
}
- else if (name.equals("fileTypeName")) {
+ else if (name.equals("fileType")) {
fileType = reader.nextString();
}
else if (name.equals("content")) {
@@ -145,7 +172,7 @@ final class DiffHttpService extends RestService {
}
reader.endObject();
- if (content == null) {;
+ if (content == null) {
return "content is not specified";
}
diff --git a/platform/platform-impl/src/org/jetbrains/ide/RestService.java b/platform/platform-impl/src/org/jetbrains/ide/RestService.java
index 7c21f5d90900..5f4f29a6c085 100644
--- a/platform/platform-impl/src/org/jetbrains/ide/RestService.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/RestService.java
@@ -24,6 +24,7 @@ import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.util.ExceptionUtil;
+import com.intellij.util.containers.ContainerUtil;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
@@ -33,7 +34,12 @@ import org.jetbrains.io.Responses;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.List;
+/**
+ * Document your service using apiDoc. To extract big example from source code, consider to use *.coffee file near your source file.
+ * (or Python/Ruby, but coffee recommended because it's plugin is lightweight). See {@link AboutHttpService} for example.
+ */
public abstract class RestService extends HttpRequestHandler {
protected static final Logger LOG = Logger.getInstance(RestService.class);
@@ -55,18 +61,21 @@ public abstract class RestService extends HttpRequestHandler {
return true;
}
else {
- char c = uri.charAt(minLength + 1);
+ char c = uri.charAt(minLength);
return c == '/' || c == '?';
}
}
return false;
}
- protected abstract boolean isMethodSupported(@NotNull HttpMethod method);
-
@NotNull
+ /**
+ * Use human-readable name or UUID if it is an internal service.
+ */
protected abstract String getServiceName();
+ protected abstract boolean isMethodSupported(@NotNull HttpMethod method);
+
@Override
public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
try {
@@ -115,4 +124,15 @@ public abstract class RestService extends HttpRequestHandler {
}
return project;
}
+
+ protected static boolean getBooleanParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
+ List values = urlDecoder.parameters().get(name);
+ if (ContainerUtil.isEmpty(values)) {
+ return false;
+ }
+
+ String value = values.get(values.size() - 1);
+ // if just name specified, so, true
+ return value.isEmpty() || Boolean.parseBoolean(value);
+ }
}
diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml
index efc3fa5c07bb..c1adabb5dda0 100644
--- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml
+++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml
@@ -341,6 +341,7 @@
+