Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add copy operation command #4778

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.clone()));
}

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),
),
);
}
Loading