Skip to content
Merged
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
132 changes: 25 additions & 107 deletions conformance/mock-provider/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use sha2::{Digest, Sha256};

use crate::project::documents_for_request;
use crate::puzzle::{
self, ARTIFACT_FORMAT, COMPILE_ARTIFACT_FORMATS, KIND_OP, KIND_PUZZLE, LookupScope,
ParseOutput, Position, Puzzle, Range, SUMMARY_ARTIFACT_FORMAT, SourceText, Symbol,
compile_artifact, is_valid_identifier, parse_document, reconstruct_source, summary_artifact,
symbol_at,
self, ARTIFACT_FORMAT, COMPILE_ARTIFACT_FORMATS, LookupScope, ParseOutput, Position, Puzzle,
Range, SUMMARY_ARTIFACT_FORMAT, SourceText, Symbol, compile_artifact, is_valid_identifier,
parse_document, reconstruct_source, summary_artifact,
};
use crate::rpc::{HandlerError, parse_params};
use crate::server::{LANGUAGE_ID, Server};
Expand Down Expand Up @@ -172,7 +171,7 @@ impl Server {
let symbols = parsed
.puzzle
.as_ref()
.map(puzzle_symbols)
.map(Puzzle::document_symbols)
.unwrap_or_default();
document_view(doc, "symbols", json!(symbols))
})
Expand All @@ -188,15 +187,9 @@ impl Server {
"definition.noSymbolAtPosition",
&params.document.uri,
)?;
let range = match symbol {
Symbol::Puzzle { range, .. } => range,
Symbol::Op { name } => {
puzzle
.op(&name)
.expect("resolved puzzles reference declared ops")
.name_range
}
};
let range = puzzle
.declaration_range(&symbol)
.expect("resolved puzzles reference declared ops");
Ok(json!({
"locations": [{ "uri": params.document.uri, "range": range }]
}))
Expand All @@ -210,20 +203,11 @@ impl Server {
"references.noSymbolAtPosition",
&params.document.uri,
)?;
let name = symbol.name();
let mut locations = Vec::new();
if params.include_declaration {
if let Some(op) = puzzle.op(name) {
locations.push(location_json(&params.document.uri, op.name_range));
}
}
locations.extend(
puzzle
.solution
.iter()
.filter(|entry| entry.name == name)
.map(|entry| location_json(&params.document.uri, entry.range)),
);
let locations: Vec<Value> = puzzle
.occurrences(symbol.name(), params.include_declaration)
.into_iter()
.map(|range| location_json(&params.document.uri, range))
.collect();
Ok(json!({ "locations": locations }))
}

Expand Down Expand Up @@ -256,34 +240,23 @@ impl Server {
&params.position_document_uri,
)?;

let name = symbol.name().to_string();
let target = match symbol {
Symbol::Puzzle { .. } => RenameTarget::Puzzle(name),
Symbol::Op { .. } => {
if puzzle.op(&name).is_some()
&& name != params.new_name
&& puzzle.op(&params.new_name).is_some()
{
return Err(HandlerError::refusal(
"rename.nameCollision",
json!({ "newName": params.new_name }),
"new name collides with an existing symbol",
));
}
RenameTarget::Op(name)
}
};
if puzzle.rename_collides(&symbol, &params.new_name) {
return Err(HandlerError::refusal(
"rename.nameCollision",
json!({ "newName": params.new_name }),
"new name collides with an existing symbol",
));
}

let edits: Vec<Value> = sorted_entries(&params.documents)
.into_iter()
.filter_map(|(key, doc)| {
let puzzle = parse_document(&doc.text).puzzle?;
let edits = rename_edits(
&puzzle,
&target,
&params.new_name,
key == &params.position_document_uri,
);
let edits: Vec<Value> = puzzle
.rename_ranges(&symbol, key == &params.position_document_uri)
.into_iter()
.map(|range| json!({ "range": range, "newText": params.new_name }))
.collect();
(!edits.is_empty()).then(|| {
json!({
"documentUri": doc.uri,
Expand Down Expand Up @@ -376,12 +349,6 @@ impl Server {
/// The provider-side bound applied when a request omits `limit`.
const DEFAULT_LOOKUP_LIMIT: u32 = 20;

#[derive(Debug)]
enum RenameTarget {
Puzzle(String),
Op(String),
}

fn select_artifact_format(accepted: Option<Vec<String>>) -> Result<&'static str, HandlerError> {
match accepted {
None => Ok(ARTIFACT_FORMAT),
Expand Down Expand Up @@ -471,22 +438,6 @@ fn location_json(uri: &str, range: Range) -> Value {
json!({ "uri": uri, "range": range })
}

fn puzzle_symbols(puzzle: &Puzzle) -> Vec<Value> {
let mut symbols = vec![json!({
"name": puzzle.name,
"kind": KIND_PUZZLE,
"range": puzzle.name_range,
})];
symbols.extend(puzzle.ops.iter().map(|op| {
json!({
"name": op.name,
"kind": KIND_OP,
"range": op.name_range,
})
}));
symbols
}

/// The shared definition/references/rename preamble: document checks, then
/// the symbol under `position`, or the method's `noSymbolAtPosition` refusal.
fn symbol_at_position(
Expand All @@ -509,39 +460,6 @@ fn symbol_at_position(
let puzzle = parse_document(&doc.text)
.into_puzzle()
.ok_or_else(no_symbol)?;
let symbol = symbol_at(&src, &puzzle, byte).ok_or_else(no_symbol)?;
let symbol = puzzle.symbol_at(&src, byte).ok_or_else(no_symbol)?;
Ok((puzzle, symbol))
}

fn rename_edits(
puzzle: &Puzzle,
target: &RenameTarget,
new_name: &str,
is_position_doc: bool,
) -> Vec<Value> {
let edit = |range: Range| json!({ "range": range, "newText": new_name });
match target {
RenameTarget::Puzzle(name) => {
if is_position_doc && puzzle.name == *name {
vec![edit(puzzle.name_range)]
} else {
Vec::new()
}
}
RenameTarget::Op(name) => {
let mut edits: Vec<Value> = puzzle
.op(name)
.map(|op| edit(op.name_range))
.into_iter()
.collect();
edits.extend(
puzzle
.solution
.iter()
.filter(|entry| entry.name == *name)
.map(|entry| edit(entry.range)),
);
edits
}
}
}
80 changes: 19 additions & 61 deletions conformance/mock-provider/src/puzzle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod catalog;
mod edits;
mod lex;
mod parse;
mod symbols;
mod text;

pub(crate) use artifact::{
Expand All @@ -19,6 +20,7 @@ pub(crate) use artifact::{
pub(crate) use catalog::{LookupScope, lookup};
pub(crate) use edits::{EditValidation, validate_edits};
pub(crate) use parse::parse_document;
pub(crate) use symbols::{Symbol, is_valid_identifier};
pub(crate) use text::{Position, Range, SourceText};

#[cfg(test)]
Expand Down Expand Up @@ -49,7 +51,7 @@ pub(crate) fn diagnostic(
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OpKind {
enum OpKind {
Add,
Sub,
Mul,
Expand Down Expand Up @@ -77,32 +79,32 @@ impl OpKind {
}

#[derive(Debug, Clone)]
pub(crate) struct Op {
pub name: String,
pub name_range: Range,
pub kind: OpKind,
pub arg: i64,
struct Op {
name: String,
name_range: Range,
kind: OpKind,
arg: i64,
}

#[derive(Debug, Clone)]
pub(crate) struct SolutionEntry {
pub name: String,
pub range: Range,
struct SolutionEntry {
name: String,
range: Range,
}

#[derive(Debug, Clone)]
pub(crate) struct Puzzle {
pub name: String,
pub name_range: Range,
pub target: i64,
pub start: i64,
pub ops: Vec<Op>,
pub solution: Vec<SolutionEntry>,
pub solution_range: Range,
name: String,
name_range: Range,
target: i64,
start: i64,
ops: Vec<Op>,
solution: Vec<SolutionEntry>,
solution_range: Range,
}

impl Puzzle {
pub(crate) fn op(&self, name: &str) -> Option<&Op> {
fn op(&self, name: &str) -> Option<&Op> {
self.ops.iter().find(|op| op.name == name)
}

Expand Down Expand Up @@ -135,47 +137,3 @@ impl ParseOutput {
self.diagnostics.iter().any(|d| d.severity == "error")
}
}

pub(crate) const KIND_PUZZLE: &str = "puzzle";
pub(crate) const KIND_OP: &str = "op";

#[derive(Debug, Clone)]
pub(crate) enum Symbol {
Puzzle { name: String, range: Range },
Op { name: String },
}

impl Symbol {
pub(crate) fn name(&self) -> &str {
match self {
Symbol::Puzzle { name, .. } | Symbol::Op { name } => name,
}
}
}

/// The symbol under `byte`: the puzzle name, an op declaration, or a solution
/// reference, in that order.
pub(crate) fn symbol_at(src: &SourceText<'_>, puzzle: &Puzzle, byte: usize) -> Option<Symbol> {
if src.contains_byte(puzzle.name_range, byte) {
return Some(Symbol::Puzzle {
name: puzzle.name.clone(),
range: puzzle.name_range,
});
}
puzzle
.ops
.iter()
.map(|op| (op.name_range, &op.name))
.chain(puzzle.solution.iter().map(|e| (e.range, &e.name)))
.find(|(range, _)| src.contains_byte(*range, byte))
.map(|(_, name)| Symbol::Op { name: name.clone() })
}

pub(crate) fn is_valid_identifier(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
(first.is_ascii_alphabetic() || first == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
Loading
Loading