diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java index 7083ec6e00dd..8dc376968c0a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java @@ -21,6 +21,7 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorCustomElementRenderer; +import com.intellij.openapi.editor.EditorCustomTextElementRenderer; import com.intellij.openapi.editor.Inlay; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.editor.markup.TextAttributes; @@ -124,7 +125,7 @@ public class ParameterHintsPresentationManager implements Disposable { return metrics; } - private static class MyRenderer implements EditorCustomElementRenderer { + private static class MyRenderer implements EditorCustomTextElementRenderer { private String myText; private int startWidth; private int steps; @@ -147,6 +148,12 @@ public class ParameterHintsPresentationManager implements Disposable { step = 1; steps = Math.max(1, Math.abs(endWidth - startWidth) / metrics.charWidth('a') / ANIMATION_CHARS_PER_STEP); } + + @NotNull + @Override + public String getText() { + return myText; + } public boolean nextStep() { return ++step <= steps; diff --git a/java/java-impl/src/com/intellij/reporting/ReportMissingOrExcessiveInlineHint.kt b/java/java-impl/src/com/intellij/reporting/ReportMissingOrExcessiveInlineHint.kt new file mode 100644 index 000000000000..8d3b93ac4694 --- /dev/null +++ b/java/java-impl/src/com/intellij/reporting/ReportMissingOrExcessiveInlineHint.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2000-2016 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 com.intellij.reporting + +import com.intellij.codeInsight.hint.HintManager +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.components.ApplicationComponent +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorCustomTextElementRenderer +import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.registry.Registry +import java.io.File + + +class InlineHintReporterRegistration : ApplicationComponent { + + override fun getComponentName() = "Inline Hints Reporter Initializer" + + override fun initComponent() { + if (!isHintsEnabled()) return + + val manager = ActionManager.getInstance() + val action = ReportMissingOrExcessiveInlineHint() + manager.registerAction("ReportMissingOrExcessiveInlineHint", action) + + val group = manager.getAction("EditorPopupMenu") as DefaultActionGroup + group.add(action) + } + + override fun disposeComponent() { + } + +} + +class ReportMissingOrExcessiveInlineHint : AnAction() { + + private val text = "Report Missing or Excessive Inline Hint" + private val description = "Text line at caret will be anonymously reported to our servers" + + init { + val presentation = templatePresentation + presentation.text = text + presentation.description = description + } + + private val recorderId = "inline-hints-reports" + private val file = File(PathManager.getTempPath(), recorderId) + + override fun update(e: AnActionEvent) { + if (!isHintsEnabled()) return + val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return + + val range = getCurrentLineRange(editor) + if (editor.getInlays(range).isNotEmpty()) { + e.presentation.isEnabled = true + } + } + + override fun actionPerformed(e: AnActionEvent) { + val editor = CommonDataKeys.EDITOR.getData(e.dataContext)!! + val document = editor.document + + val range = getCurrentLineRange(editor) + val inlays = editor.getInlays(range) + + if (inlays.isNotEmpty()) { + val line = document.getText(range) + reportInlays(line.trim(), inlays) + showHint(editor) + } + } + + private fun reportInlays(text: String, inlays: List) { + val hints = inlays + .map { it.renderer } + .mapNotNull { if (it is EditorCustomTextElementRenderer) it else null } + .map { it.text } + + trySend(text, hints) + } + + private fun trySend(text: String, inlays: List) { + val report = InlayReport(text, inlays) + writeToFile(createReportLine(recorderId, report)) + trySendFileInBackground() + } + + private fun trySendFileInBackground() { + if (!file.exists() || file.length() == 0L) return + + ApplicationManager.getApplication().executeOnPooledThread { + val text = file.readText() + if (StatsSender.send(text)) { + file.delete() + } + } + } + + private fun showHint(editor: Editor) { + ApplicationManager.getApplication().invokeLater { + HintManager.getInstance().showInformationHint(editor, "Troubled inline hint was reported") + } + } + + private fun writeToFile(line: String) { + if (!file.exists()) { + file.createNewFile() + } + file.appendText(line) + } + + private fun getCurrentLineRange(editor: Editor): TextRange { + val offset = editor.caretModel.currentCaret.offset + val document = editor.document + val line = document.getLineNumber(offset) + return TextRange(document.getLineStartOffset(line), document.getLineEndOffset(line)) + } + +} + +private fun isHintsEnabled() = Registry.`is`("editor.inline.parameter.hints") + +private fun Editor.getInlays(range: TextRange): List { + return inlayModel.getInlineElementsInRange(range.startOffset, range.endOffset) +} + +private class InlayReport(var text: String, var inlays: List) \ No newline at end of file diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/EditorCustomTextElementRenderer.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/EditorCustomTextElementRenderer.java new file mode 100644 index 000000000000..ce10c1b27a90 --- /dev/null +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/EditorCustomTextElementRenderer.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2016 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 com.intellij.openapi.editor; + +import org.jetbrains.annotations.NotNull; + +public interface EditorCustomTextElementRenderer extends EditorCustomElementRenderer { + @NotNull + String getText(); +} diff --git a/platform/platform-impl/src/com/intellij/reporting/Reporter.kt b/platform/platform-impl/src/com/intellij/reporting/Reporter.kt new file mode 100644 index 000000000000..5da107a0da14 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/reporting/Reporter.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2016 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 com.intellij.reporting + +import com.google.gson.Gson +import com.intellij.openapi.application.PermanentInstallationID +import org.apache.http.client.fluent.Request +import org.apache.http.entity.ContentType + + +private class StatsServerInfo(var status: String, var url: String) { + fun isServiceAlive() = "ok" == status +} + +private object Utils { + val gson = Gson() +} + + +object StatsSender { + private val infoUrl = "https://www.jetbrains.com/config/features-service-status.json" + + private fun requestServerUrl(): String? { + try { + val response = Request.Get(infoUrl).execute().returnContent().asString() + val info = Utils.gson.fromJson(response, StatsServerInfo::class.java) + if (info.isServiceAlive()) return info.url + } + catch (e: Exception) { + } + + return null + } + + fun send(text: String): Boolean { + val url = requestServerUrl() ?: return false + try { + val response = Request.Post(url).bodyString(text, ContentType.TEXT_HTML).execute() + val code = response.handleResponse { it.statusLine.statusCode } + if (code >= 200 && code < 300) { + return true + } + } + catch (e: Exception) { + } + return false + } + +} + +fun createReportLine(recorderId: String, data: T): String { + val json = Utils.gson.toJson(data) + val userUid = PermanentInstallationID.get() + val stamp = System.currentTimeMillis() + return "$stamp\t$recorderId\t$userUid\trandom_session_id\t$json" +} \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 8b04c3decd0c..67de654174c9 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -51,6 +51,9 @@ com.intellij.util.xml.impl.JavaDomApplicationComponent + + com.intellij.reporting.InlineHintReporterRegistration +