Skip to content

Commit

Permalink
Add copy operation command (#4778)
Browse files Browse the repository at this point in the history
Summary:
I often find myself in a situation where I want to quickly run a Relay query outside of the configured Relay environment, i.e. a GraphQL IDE or another tool. When not using persisted operations, I need to go to the artifact, copy out the value of the `text` property and get rid of all the `\n` line breaks in the text. For persisted operations it's less tedious, I just grab the `id` from the artifact and look up the operation in the operation store, but it's still more cumbersome than it has to be.

This implements a new `relay/printOperation` request in the LSP that returns the transformed operation text of the operation under the cursor or the first operation inside the document. The VS Code Extension is also updated with a `relay.copyOperation` command that invokes that LSP request and copies the operation text to the clipboard.

<img src="https://github.com/user-attachments/assets/52cdff85-dad2-4236-b4d8-c155c02c2c97" width="500" alt="Example of the command in action" />

Pull Request resolved: #4778

Reviewed By: tyao1

Differential Revision: D63033129

Pulled By: captbaritone

fbshipit-source-id: d5caf83b4b0135e4b30ce8cfaf4ef23108f52db6
  • Loading branch information
tobias-tengler authored and facebook-github-bot committed Sep 27, 2024
1 parent 36eecfe commit 8d2380b
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 1 deletion.
2 changes: 1 addition & 1 deletion compiler/crates/relay-lsp/src/graphql_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl Request for GraphQLExecuteQuery {
/// This function will return the program that contains only operation
/// and all referenced fragments.
/// We can use it to print the full query text
fn get_operation_only_program(
pub fn get_operation_only_program(
operation: Arc<OperationDefinition>,
fragments: Vec<Arc<FragmentDefinition>>,
program: &Program,
Expand Down
1 change: 1 addition & 0 deletions compiler/crates/relay-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod lsp_extra_data_provider;
pub mod lsp_process_error;
pub mod lsp_runtime_error;
pub mod node_resolution_info;
pub mod print_operation;
pub mod references;
pub mod rename;
mod resolved_types_at_location;
Expand Down
92 changes: 92 additions & 0 deletions compiler/crates/relay-lsp/src/print_operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

use graphql_ir::OperationDefinitionName;
use lsp_types::request::Request;
use lsp_types::TextDocumentPositionParams;
use serde::Deserialize;
use serde::Serialize;

use crate::GlobalState;
use crate::LSPRuntimeError;
use crate::LSPRuntimeResult;

pub(crate) fn on_print_operation(
state: &impl GlobalState,
params: <PrintOperation as Request>::Params,
) -> LSPRuntimeResult<<PrintOperation as Request>::Result> {
let text_document_uri = params
.text_document_position_params
.text_document
.uri
.clone();

let project_name = state.extract_project_name_from_url(&text_document_uri)?;
let executable_document_under_cursor =
state.extract_executable_document_from_text(&params.text_document_position_params, 1);

let operation_name = match executable_document_under_cursor {
Ok((document, _)) => {
get_first_operation_name(&document.definitions).ok_or(LSPRuntimeError::ExpectedError)
}
Err(_) => {
let executable_definitions =
state.resolve_executable_definitions(&text_document_uri)?;

if executable_definitions.is_empty() {
return Err(LSPRuntimeError::ExpectedError);
}

get_first_operation_name(&executable_definitions).ok_or(LSPRuntimeError::ExpectedError)
}
}?;

state
.get_operation_text(operation_name, &project_name)
.map(|operation_text| PrintOperationResponse {
operation_name: operation_name.0.to_string(),
operation_text,
})
}

fn get_first_operation_name(
executable_definitions: &[graphql_syntax::ExecutableDefinition],
) -> Option<OperationDefinitionName> {
executable_definitions.iter().find_map(|definition| {
if let graphql_syntax::ExecutableDefinition::Operation(operation) = definition {
if let Some(name) = &operation.name {
return Some(OperationDefinitionName(name.value));
}

None
} else {
None
}
})
}

pub(crate) enum PrintOperation {}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PrintOperationParams {
#[serde(flatten)]
pub text_document_position_params: TextDocumentPositionParams,
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PrintOperationResponse {
pub operation_name: String,
pub operation_text: String,
}

impl Request for PrintOperation {
type Params = PrintOperationParams;
type Result = PrintOperationResponse;
const METHOD: &'static str = "relay/printOperation";
}
3 changes: 3 additions & 0 deletions compiler/crates/relay-lsp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ use crate::hover::on_hover;
use crate::inlay_hints::on_inlay_hint_request;
use crate::lsp_process_error::LSPProcessResult;
use crate::lsp_runtime_error::LSPRuntimeError;
use crate::print_operation::on_print_operation;
use crate::print_operation::PrintOperation;
use crate::references::on_references;
use crate::rename::on_prepare_rename;
use crate::rename::on_rename;
Expand Down Expand Up @@ -258,6 +260,7 @@ fn dispatch_request(request: lsp_server::Request, lsp_state: &impl GlobalState)
.on_request_sync::<GetSourceLocationOfTypeDefinition>(
on_get_source_location_of_type_definition,
)?
.on_request_sync::<PrintOperation>(on_print_operation)?
.on_request_sync::<HoverRequest>(on_hover)?
.on_request_sync::<GotoDefinition>(on_goto_definition)?
.on_request_sync::<References>(on_references)?
Expand Down
59 changes: 59 additions & 0 deletions compiler/crates/relay-lsp/src/server/lsp_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ use fnv::FnvBuildHasher;
use graphql_ir::build_ir_with_extra_features;
use graphql_ir::BuilderOptions;
use graphql_ir::FragmentVariablesSemantic;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::RelayMode;
use graphql_syntax::parse_executable_with_error_recovery_and_parser_features;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::ExecutableDocument;
use graphql_syntax::GraphQLSource;
use graphql_text_printer::print_full_operation;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use log::debug;
Expand All @@ -44,6 +46,7 @@ use relay_compiler::FileGroup;
use relay_compiler::ProjectName;
use relay_docblock::parse_docblock_ast;
use relay_docblock::ParseOptions;
use relay_transforms::apply_transforms;
use relay_transforms::deprecated_fields_for_executable_definition;
use schema::SDLSchema;
use schema_documentation::CombinedSchemaDocumentation;
Expand All @@ -54,6 +57,7 @@ use tokio::sync::Notify;
use super::task_queue::TaskScheduler;
use crate::diagnostic_reporter::DiagnosticReporter;
use crate::docblock_resolution_info::create_docblock_resolution_info;
use crate::graphql_tools::get_operation_only_program;
use crate::graphql_tools::get_query_text;
use crate::location::transform_relay_location_to_lsp_location_with_cache;
use crate::lsp_runtime_error::LSPRuntimeResult;
Expand Down Expand Up @@ -129,6 +133,12 @@ pub trait GlobalState {
project_name: &StringKey,
) -> LSPRuntimeResult<String>;

fn get_operation_text(
&self,
operation_name: OperationDefinitionName,
project_name: &StringKey,
) -> LSPRuntimeResult<String>;

fn document_opened(&self, url: &Url, text: &str) -> LSPRuntimeResult<()>;

fn document_changed(&self, url: &Url, text: &str) -> LSPRuntimeResult<()>;
Expand Down Expand Up @@ -575,6 +585,55 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
get_query_text(self, query_text, (*project_name).into())
}

fn get_operation_text(
&self,
operation_name: OperationDefinitionName,
project_name: &StringKey,
) -> LSPRuntimeResult<String> {
let project_config = self
.config
.enabled_projects()
.find(|project_config| project_config.name == (*project_name).into())
.ok_or_else(|| {
LSPRuntimeError::UnexpectedError(format!(
"Unable to get project config for project {}.",
project_name
))
})?;

let program = self.get_program(project_name)?;

let operation_only_program = program
.operation(operation_name)
.and_then(|operation| {
get_operation_only_program(Arc::clone(operation), vec![], &program)
})
.ok_or(LSPRuntimeError::ExpectedError)?;

let programs = apply_transforms(
project_config,
Arc::new(operation_only_program),
Default::default(),
Arc::clone(&self.perf_logger),
None,
self.config.custom_transforms.as_ref(),
)
.map_err(|_| LSPRuntimeError::ExpectedError)?;

let operation_to_print = programs
.operation_text
.operation(operation_name)
.ok_or(LSPRuntimeError::ExpectedError)?;

let operation_text = print_full_operation(
&programs.operation_text,
operation_to_print,
Default::default(),
);

Ok(operation_text)
}

fn document_opened(&self, uri: &Url, text: &str) -> LSPRuntimeResult<()> {
let file_group =
get_file_group_from_uri(&self.file_categorizer, uri, &self.root_dir, &self.config)?;
Expand Down
4 changes: 4 additions & 0 deletions vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
{
"command": "relay.stopCompiler",
"title": "Relay: Stop Compiler"
},
{
"command": "relay.copyOperation",
"title": "Relay: Copy Operation"
}
],
"configuration": {
Expand Down
62 changes: 62 additions & 0 deletions vscode-extension/src/commands/copyOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as semver from 'semver';
import {window, env} from 'vscode';
import {RequestType, TextDocumentPositionParams} from 'vscode-languageclient';
import {RelayExtensionContext} from '../context';

export function handleCopyOperation(context: RelayExtensionContext): void {
const {binaryVersion} = context.relayBinaryExecutionOptions;

if (binaryVersion) {
const isSupportedCompilerVersion =
semver.satisfies(binaryVersion, '>18.0') ||
semver.prerelease(binaryVersion) != null;

if (!isSupportedCompilerVersion) {
window.showWarningMessage(
'Unsupported relay-compiler version. Requires >18.0.0',
);
return;
}
}

if (!context.client || !context.client.isRunning()) {
return;
}

const activeEditor = window.activeTextEditor;

if (!activeEditor) {
return;
}

const request = new RequestType<
TextDocumentPositionParams,
PrintOperationResponse,
void
>('relay/printOperation');

const params: TextDocumentPositionParams = {
textDocument: {uri: activeEditor.document.uri.toString()},
position: activeEditor.selection.active,
};

context.client.sendRequest(request, params).then(response => {
env.clipboard.writeText(response.operationText).then(() => {
window.showInformationMessage(
`Copied operation "${response.operationName}" to clipboard`,
);
});
});
}

type PrintOperationResponse = {
operationName: string;
operationText: string;
};
5 changes: 5 additions & 0 deletions vscode-extension/src/commands/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {handleRestartLanguageServerCommand} from './restart';
import {handleShowOutputCommand} from './showOutput';
import {handleStartCompilerCommand} from './startCompiler';
import {handleStopCompilerCommand} from './stopCompiler';
import {handleCopyOperation} from './copyOperation';

export function registerCommands(context: RelayExtensionContext) {
context.extensionContext.subscriptions.push(
Expand All @@ -30,5 +31,9 @@ export function registerCommands(context: RelayExtensionContext) {
'relay.showOutput',
handleShowOutputCommand.bind(null, context),
),
commands.registerCommand(
'relay.copyOperation',
handleCopyOperation.bind(null, context),
),
);
}

0 comments on commit 8d2380b

Please sign in to comment.