-
Notifications
You must be signed in to change notification settings - Fork 380
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
6563: ACT: introduce `Share in Playground` action r=ortem a=Undin These changes: * Introduce `Share in Playground` action to share your code in https://play.rust-lang.org/. The action correctly handles selected text in editor (i.e. share only selected text), current edition and toolchain channel. * Add `Rust` action group to have a single item in context menus for util Rust actions. `Reformat File with Rustfmt`, `Reformat Cargo Project with Rustfmt`, `Rust REPL`, `Share in Playground` are placed into this group as well as `Show expanded Macro` actions. Also, the group is added to `Tools` menu. * Provide `MockServerFixture` to help setting up mock web server in test to check network interactions https://user-images.githubusercontent.com/2539310/103181788-265fea80-48b6-11eb-87f7-2ba813cfb596.mov changelog: Introduce `Share in Playground` action to share your code in https://play.rust-lang.org/. You can invoke it via `Tools | Rust | Share in Playground` or via context menu Co-authored-by: Arseniy Pendryak <a.pendryak@yandex.ru>
- Loading branch information
Showing
11 changed files
with
441 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
src/main/kotlin/org/rust/ide/actions/RsToolsActionGroup.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* Use of this source code is governed by the MIT license that can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
package org.rust.ide.actions | ||
|
||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.actionSystem.DefaultActionGroup | ||
import com.intellij.openapi.project.DumbAware | ||
import org.rust.cargo.runconfig.hasCargoProject | ||
|
||
class RsToolsActionGroup : DefaultActionGroup(), DumbAware { | ||
|
||
override fun update(e: AnActionEvent) { | ||
val project = e.project ?: return | ||
e.presentation.isEnabledAndVisible = project.hasCargoProject | ||
} | ||
} |
161 changes: 161 additions & 0 deletions
161
src/main/kotlin/org/rust/ide/actions/ShareInPlaygroundAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
* Use of this source code is governed by the MIT license that can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
package org.rust.ide.actions | ||
|
||
import com.google.common.annotations.VisibleForTesting | ||
import com.google.gson.Gson | ||
import com.intellij.ide.util.PropertiesComponent | ||
import com.intellij.notification.NotificationAction | ||
import com.intellij.notification.NotificationListener | ||
import com.intellij.notification.NotificationType | ||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.actionSystem.CommonDataKeys | ||
import com.intellij.openapi.application.runReadAction | ||
import com.intellij.openapi.ide.CopyPasteManager | ||
import com.intellij.openapi.progress.ProgressIndicator | ||
import com.intellij.openapi.progress.Task | ||
import com.intellij.openapi.project.DumbAwareAction | ||
import com.intellij.openapi.ui.DialogWrapper | ||
import com.intellij.openapi.ui.Messages | ||
import com.intellij.openapiext.isUnitTestMode | ||
import com.intellij.util.io.HttpRequests | ||
import org.jetbrains.annotations.TestOnly | ||
import org.rust.RsBundle | ||
import org.rust.cargo.project.workspace.CargoWorkspace | ||
import org.rust.ide.notifications.showBalloon | ||
import org.rust.ide.utils.USER_AGENT | ||
import org.rust.lang.core.psi.RsFile | ||
import org.rust.openapiext.JsonUtils | ||
import java.awt.datatransfer.StringSelection | ||
|
||
class ShareInPlaygroundAction : DumbAwareAction() { | ||
|
||
override fun update(e: AnActionEvent) { | ||
val file = e.getData(CommonDataKeys.PSI_FILE) as? RsFile | ||
e.presentation.isEnabledAndVisible = file != null | ||
} | ||
|
||
override fun actionPerformed(e: AnActionEvent) { | ||
val project = e.project ?: return | ||
val file = e.getData(CommonDataKeys.PSI_FILE) as? RsFile ?: return | ||
val editor = e.getData(CommonDataKeys.EDITOR) | ||
|
||
val (text, hasSelection) = runReadAction { | ||
val selectedText = editor?.selectionModel?.selectedText | ||
if (selectedText != null) { | ||
selectedText to true | ||
} else { | ||
file.text to false | ||
} | ||
} | ||
|
||
if (!confirmShare(file, hasSelection)) return | ||
|
||
val channel = file.cargoProject?.rustcInfo?.version?.channel?.channel ?: "stable" | ||
val edition = (file.crate?.edition ?: CargoWorkspace.Edition.EDITION_2018).presentation | ||
|
||
object : Task.Backgroundable(project, RsBundle.message("action.Rust.ShareInPlayground.progress.title")) { | ||
|
||
@Volatile | ||
private var gistId: String? = null | ||
|
||
override fun shouldStartInBackground(): Boolean = true | ||
override fun run(indicator: ProgressIndicator) { | ||
val json = Gson().toJson(PlaygroundCode(text)) | ||
val response = HttpRequests.post("$playgroundHost/meta/gist/", "application/json") | ||
.userAgent(USER_AGENT) | ||
.connect { | ||
it.write(json) | ||
it.readString(indicator) | ||
} | ||
|
||
gistId = JsonUtils.parseJsonObject(response).getAsJsonPrimitive("id").asString | ||
} | ||
|
||
override fun onSuccess() { | ||
val url = "https://play.rust-lang.org/?version=$channel&edition=$edition&gist=$gistId" | ||
val copyUrlAction = NotificationAction.createSimple(RsBundle.message("action.Rust.ShareInPlayground.notification.copy.url.text")) { | ||
CopyPasteManager.getInstance().setContents(StringSelection(url)) | ||
} | ||
project.showBalloon( | ||
RsBundle.message("action.Rust.ShareInPlayground.notification.title"), | ||
RsBundle.message("action.Rust.ShareInPlayground.notification.text", url), | ||
NotificationType.INFORMATION, | ||
copyUrlAction, | ||
NotificationListener.URL_OPENING_LISTENER | ||
) | ||
} | ||
|
||
override fun onThrowable(error: Throwable) { | ||
if (!isUnitTestMode) { | ||
super.onThrowable(error) | ||
} | ||
project.showBalloon( | ||
RsBundle.message("action.Rust.ShareInPlayground.notification.title"), | ||
RsBundle.message("action.Rust.ShareInPlayground.notification.error"), | ||
NotificationType.ERROR | ||
) | ||
} | ||
}.queue() | ||
} | ||
|
||
private fun confirmShare(file: RsFile, hasSelection: Boolean): Boolean { | ||
val showConfirmation = PropertiesComponent.getInstance().getBoolean(SHOW_SHARE_IN_PLAYGROUND_CONFIRMATION, true) | ||
if (!showConfirmation) { | ||
return true | ||
} | ||
val doNotAskOption = object : DialogWrapper.DoNotAskOption.Adapter() { | ||
override fun rememberChoice(isSelected: Boolean, exitCode: Int) { | ||
if (isSelected && exitCode == Messages.OK) { | ||
PropertiesComponent.getInstance().setValue(SHOW_SHARE_IN_PLAYGROUND_CONFIRMATION, false, true) | ||
} | ||
} | ||
} | ||
|
||
val message = if (hasSelection) { | ||
RsBundle.message("action.Rust.ShareInPlayground.confirmation.selected.text") | ||
} else { | ||
RsBundle.message("action.Rust.ShareInPlayground.confirmation", file.name) | ||
} | ||
|
||
val answer = Messages.showOkCancelDialog( | ||
message, | ||
RsBundle.message("action.Rust.ShareInPlayground.text"), | ||
Messages.getOkButton(), | ||
Messages.getCancelButton(), | ||
Messages.getQuestionIcon(), | ||
doNotAskOption | ||
) | ||
return answer == Messages.OK | ||
} | ||
|
||
companion object { | ||
private const val SHOW_SHARE_IN_PLAYGROUND_CONFIRMATION = "rs.show.share.in.playground.confirmation" | ||
} | ||
|
||
@VisibleForTesting | ||
data class PlaygroundCode(val code: String) | ||
} | ||
|
||
private var MOCK: String? = null | ||
|
||
private val playgroundHost: String get() { | ||
return if (isUnitTestMode) { | ||
MOCK ?: error("Use `withMockPlaygroundHost`") | ||
} else { | ||
"https://play.rust-lang.org" | ||
} | ||
} | ||
|
||
@TestOnly | ||
fun withMockPlaygroundHost(host: String, action: () -> Unit) { | ||
MOCK = host | ||
try { | ||
action() | ||
} finally { | ||
MOCK = null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* Use of this source code is governed by the MIT license that can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
package org.rust.ide.utils | ||
|
||
const val USER_AGENT = "IntelliJ Rust Plugin (https://github.com/intellij-rust/intellij-rust)" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Use of this source code is governed by the MIT license that can be | ||
* found in the LICENSE file. | ||
*/ | ||
|
||
package org.rust | ||
|
||
import com.intellij.testFramework.fixtures.impl.BaseFixture | ||
import okhttp3.mockwebserver.Dispatcher | ||
import okhttp3.mockwebserver.MockResponse | ||
import okhttp3.mockwebserver.MockWebServer | ||
import okhttp3.mockwebserver.RecordedRequest | ||
import org.junit.Assert | ||
import org.rust.ide.utils.USER_AGENT | ||
|
||
typealias ResponseHandler = (RecordedRequest) -> MockResponse? | ||
|
||
class MockServerFixture : BaseFixture() { | ||
|
||
private var handler: ResponseHandler? = null | ||
|
||
private val mockWebServer = MockWebServer().apply { | ||
dispatcher = object : Dispatcher() { | ||
override fun dispatch(request: RecordedRequest): MockResponse { | ||
Assert.assertEquals(USER_AGENT, request.getHeader("User-Agent")) | ||
return handler?.invoke(request) ?: MockResponse().setResponseCode(404) | ||
} | ||
} | ||
} | ||
|
||
val baseUrl: String get() = mockWebServer.url("/").toString() | ||
|
||
fun withHandler(handler: ResponseHandler) { | ||
this.handler = handler | ||
} | ||
|
||
override fun tearDown() { | ||
mockWebServer.shutdown() | ||
super.tearDown() | ||
} | ||
} |
Oops, something went wrong.