Skip to main content
Glama

semantic-edit-mcp

by jbr
edit.rs8.68 kB
use super::{EditPosition, Editor}; use crate::searcher::find_positions; use fieldwork::Fieldwork; use ropey::Rope; use std::{ borrow::Cow, fmt::{self, Debug, Formatter}, }; use tree_sitter::{InputEdit, Node, Point, Tree}; #[derive(Clone, Fieldwork)] #[fieldwork(option_set_some)] pub struct Edit<'editor, 'language> { editor: &'editor Editor<'language>, tree: Tree, rope: Rope, #[field(get, set, with, get_mut(deref = false), into)] content: Cow<'editor, str>, #[field(get, get_mut)] position: EditPosition, #[field(get = is_valid)] valid: Option<bool>, #[field(get, take)] message: Option<String>, #[field(get, take)] output: Option<String>, #[field(get, set, with, take)] nodes: Option<Vec<Node<'editor>>>, #[field(with, get, set)] annotation: Option<&'static str>, } impl PartialEq for Edit<'_, '_> { fn eq(&self, other: &Edit<'_, '_>) -> bool { std::ptr::eq(self.editor, other.editor) && self.content == other.content && self.position == other.position && self.nodes == other.nodes } } impl Eq for Edit<'_, '_> {} impl<'editor, 'language> Debug for Edit<'editor, 'language> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alternate = f.alternate(); let mut s = f.debug_struct("Edit"); s.field("content", &self.content) .field("anchor", &self.editor.selector.anchor) .field("operation", &self.editor.selector.operation); if let Some(valid) = self.valid { s.field("valid", &valid); } if let Some(nodes) = &self.nodes { s.field( "node_kinds", &nodes .iter() .map(|node| node.kind().to_string()) .collect::<Vec<_>>() .join(","), ); } if let Some(edit_region) = self.edit_region() { s.field("edit_region", &edit_region); } else { let source_code = self.source_code(); let start_byte = self.position.start_byte; let min = source_code[..start_byte] .rmatch_indices('\n') .nth(2) .map(|(n, _)| n) .unwrap_or(0); let max = source_code[start_byte..] .find('\n') .map(|n| n + start_byte) .unwrap_or(source_code.len()); s.field( "insertion_point", &format!( "{}<|>{}", &source_code[min..start_byte], &source_code[start_byte..max] ), ); } if let Some(annotation) = self.annotation { s.field("annotation", &format_args!("{annotation}")); } if let Some(message) = self.message() { if alternate { s.field("message", &format_args!("{message}")); } } s.finish() } } impl<'editor, 'language> Edit<'editor, 'language> { pub fn new(editor: &'editor Editor<'language>, position: EditPosition) -> Self { Self { editor, tree: editor.tree.clone(), rope: editor.rope.clone(), position, content: Cow::Borrowed(&editor.content), valid: None, message: None, output: None, nodes: None, annotation: None, } } pub fn insert_before(mut self) -> Self { if let Some(edit_region) = self.edit_region() { if let Ok(positions) = find_positions(&self.content, edit_region) { if let Some((start, _)) = positions.last() { match &mut self.content { Cow::Borrowed(borrowed) => *borrowed = &borrowed[..*start], Cow::Owned(owned) => *owned = owned[..*start].to_string(), }; } } } self.position.end_byte = None; self } pub fn insert_after(mut self) -> Option<Self> { if let Some(edit_region) = self.edit_region() { if let Ok(positions) = find_positions(&self.content, edit_region) { if let Some((_, end)) = positions.first() { match &mut self.content { Cow::Borrowed(borrowed) => *borrowed = &borrowed[*end..], Cow::Owned(owned) => *owned = owned[*end..].to_string(), }; } } } self.position.start_byte = self.position.end_byte.take()?; Some(self) } pub fn edit_region(&self) -> Option<&'editor str> { if let EditPosition { start_byte, end_byte: Some(end_byte), } = self.position { self.editor.source_code.get(start_byte..end_byte) } else { None } } pub fn with_end_byte(mut self, end_byte: usize) -> Self { self.position.end_byte = Some(end_byte); self } fn byte_to_point(&self, byte_idx: usize) -> Point { let line = self.rope.byte_to_line(byte_idx); let line_start_byte = self.rope.line_to_byte(line); let column = byte_idx - line_start_byte; Point { row: line, column } } pub(crate) fn apply(&mut self) -> bool { if let Some(valid) = self.valid { return valid; } let content = &self.content; let EditPosition { start_byte, end_byte, } = self.position; let start_char = self.rope.byte_to_char(start_byte); let start_position = self.byte_to_point(start_byte); let (old_end_byte, old_end_position) = if let Some(old_end_byte) = end_byte { let end_char = self.rope.byte_to_char(old_end_byte); let old_end_position = self.byte_to_point(old_end_byte); self.rope.remove(start_char..end_char); (old_end_byte, old_end_position) } else { (start_byte, start_position) }; self.rope.insert(start_char, content); let new_end_byte = start_byte + content.len(); let new_end_position = self.byte_to_point(new_end_byte); self.tree.edit(&InputEdit { start_byte, old_end_byte, new_end_byte, start_position, old_end_position, new_end_position, }); let output = self.rope.to_string(); if let Some(tree) = self.editor.parse(&output, Some(&self.tree)) { self.tree = tree; } else { self.valid = Some(false); self.message = Some("Unable to parse result so no changes were made. The file is still in a good state. Try a different edit".into()); return false; } let valid = if let Some(message) = self.validate(&output) { self.message = Some(message); false } else { self.message = Some(format!( "Applied {} operation", self.editor.selector.operation_name() )); match self.editor.format_code(&output) { Ok(formatted) => { self.output = Some(formatted); true } Err(err) => { self.message = Some(err); false } } }; self.valid = Some(valid); valid } fn validate(&mut self, output: &str) -> Option<String> { let errors = self.editor.validate_tree(&self.tree, output)?; let diff = self.editor.diff(output); Some(format!( "This edit would result in invalid syntax, but the file is still in a valid state. \ No change was performed. Suggestion: Try a different change.\n {errors}\n\n{diff}" )) } pub(crate) fn source_code(&self) -> &'editor str { self.editor.source_code() } pub(crate) fn start_byte(&self) -> usize { self.position.start_byte } pub(crate) fn set_start_byte(&mut self, start_byte: usize) -> &mut Self { self.position.start_byte = start_byte; self } pub(crate) fn modify(mut fun: impl FnMut(&mut Self)) -> impl FnMut(Self) -> Self { move |mut edit| { fun(&mut edit); edit } } pub(crate) fn with_start_byte(mut self, start_byte: usize) -> Self { self.position.start_byte = start_byte; self } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jbr/semantic-edit-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server