Skip to main content
Glama

semantic-edit-mcp

by jbr
indentation.rs5.57 kB
use std::{ borrow::Cow, collections::BTreeMap, fmt::{self, Display, Formatter, Write}, }; #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub(super) enum Indentation { Spaces(u8), Tabs, } impl Display for Indentation { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Indentation::Spaces(spaces) => { for _ in 0..*spaces { f.write_char(' ')?; } Ok(()) } Indentation::Tabs => f.write_char('\t'), } } } struct LineIndent { indentation: Indentation, count: usize, } impl Display for LineIndent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { for _ in 0..self.count { Display::fmt(&self.indentation, f)?; } Ok(()) } } impl Indentation { fn counts(source: &str) -> BTreeMap<Self, usize> { let mut counts = BTreeMap::new(); let mut last_indentation = 0; for line in source.lines().take(100) { if line.starts_with('\t') { *counts.entry(Self::Tabs).or_default() += 1; } else { let current_indentation = line.chars().take_while(|c| c == &' ').count(); if line.len() != current_indentation { // ignore indentation-only lines let diff = current_indentation.abs_diff(last_indentation); last_indentation = current_indentation; if let Ok(diff) = u8::try_from(diff) { if diff > 0 { *counts.entry(Self::Spaces(diff)).or_default() += 1; } } } } } counts } pub fn determine(source: &str) -> Option<Self> { Self::counts(source) .into_iter() .max_by_key(|(_, count)| *count) .map(|(spaces, _)| spaces) } pub fn minimum(&self, source: &str) -> usize { source .lines() .filter(|s| !s.trim().is_empty()) .map(|line| self.unit_count(line)) .min() .unwrap_or(0) } /// Reindent and normalize content to a specific base level and indentation style while /// preserving relative indentation pub fn reindent<'a>( &self, target_indentation_count: usize, content: &mut Cow<'a, str>, indent_first_line: bool, ) { if content.is_empty() { return; } let (first_line, content_to_consider) = if indent_first_line { ("", &**content) } else if let Some(first_line_end) = content.find('\n') { content.split_at(first_line_end) } else { //just one line and we don't want to reindent it return; }; let content_counts = Self::counts(content_to_consider); let content_style = content_counts .iter() .max_by_key(|(_, count)| **count) .map(|(spaces, _)| *spaces) .unwrap_or(Self::Spaces(4)); let content_indentation = content_to_consider .lines() .map(|line| (content_style.unit_count(line), line)) .collect::<Vec<_>>(); let min_units = content_indentation .iter() .filter(|(_, s)| !s.trim().is_empty()) .map(|(x, _)| *x) .min() .unwrap_or(0); let consistent_style = content_counts.len() == 1; if self == &content_style && min_units == target_indentation_count && consistent_style { return; } let mut string = String::from(first_line); for (current_units, line) in content_indentation { let relative_units = current_units.saturating_sub(min_units); let new_units = target_indentation_count + relative_units; let new_indentation = LineIndent { indentation: *self, count: new_units, }; let line = line.trim_start(); writeln!(&mut string, "{new_indentation}{line}").unwrap(); } if !content.ends_with('\n') { string.pop(); } // log::trace!( // "reindented from {min_units} {content_style:?} to {target_indentation_count} {self:?}" // ); *content = Cow::Owned(string); } // fn convert_line_indentation(&self, line: &str, from_style: &Indentation) -> String { // if line.trim().is_empty() { // return line.to_string(); // } // let units = from_style.line_indentation(line); // let new_indentation = self.create_indentation(units); // format!("{}{}", new_indentation, line.trim_start()) // } pub fn unit_count(&self, line: &str) -> usize { match self { Indentation::Spaces(n) => { if *n == 0 { 0 } else { let spaces = line.chars().take_while(|c| *c == ' ').count(); spaces.div_ceil(*n as usize) } } Indentation::Tabs => line.chars().take_while(|c| *c == '\t').count(), } } // fn create_indentation(&self, units: usize) -> String { // match self { // Indentation::Spaces(n) => " ".repeat(*n as usize * units), // Indentation::Tabs => "\t".repeat(units), // } // } }

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