Skip to main content
Glama

Edit-MCP

draw_filepicker.rs9.48 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use std::cmp::Ordering; use std::fs; use std::path::{Path, PathBuf}; use edit::framebuffer::IndexedColor; use edit::helpers::*; use edit::input::vk; use edit::tui::*; use edit::{icu, path}; use crate::localization::*; use crate::state::*; pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { // The save dialog is pre-filled with the current document filename. if state.wants_file_picker == StateFilePicker::SaveAs { state.wants_file_picker = StateFilePicker::SaveAsShown; if state.file_picker_pending_name.as_os_str().is_empty() { state.file_picker_pending_name = state.documents.active().map_or("Untitled.txt", |doc| doc.filename.as_str()).into(); } } let width = (ctx.size().width - 20).max(10); let height = (ctx.size().height - 10).max(10); let mut doit = None; let mut done = false; ctx.modal_begin( "file-picker", if state.wants_file_picker == StateFilePicker::Open { loc(LocId::FileOpen) } else { loc(LocId::FileSaveAs) }, ); ctx.attr_intrinsic_size(Size { width, height }); { let mut activated = false; ctx.table_begin("path"); ctx.table_set_columns(&[0, COORD_TYPE_SAFE_MAX]); ctx.table_set_cell_gap(Size { width: 1, height: 0 }); ctx.attr_padding(Rect::two(1, 1)); ctx.inherit_focus(); { ctx.table_next_row(); ctx.label("dir-label", loc(LocId::SaveAsDialogPathLabel)); ctx.label("dir", state.file_picker_pending_dir.as_str()); ctx.attr_overflow(Overflow::TruncateMiddle); ctx.table_next_row(); ctx.inherit_focus(); ctx.label("name-label", loc(LocId::SaveAsDialogNameLabel)); ctx.editline("name", &mut state.file_picker_pending_name); ctx.inherit_focus(); if ctx.is_focused() && ctx.consume_shortcut(vk::RETURN) { activated = true; } } ctx.table_end(); if state.file_picker_entries.is_none() { draw_dialog_saveas_refresh_files(state); } let files = state.file_picker_entries.as_ref().unwrap(); ctx.scrollarea_begin( "directory", Size { width: 0, // -1 for the label (top) // -1 for the label (bottom) // -1 for the editline (bottom) height: height - 3, }, ); ctx.attr_background_rgba(ctx.indexed_alpha(IndexedColor::Black, 1, 4)); ctx.next_block_id_mixin(state.file_picker_pending_dir_revision); { ctx.list_begin("files"); ctx.inherit_focus(); for entry in files { match ctx.list_item(false, entry.as_str()) { ListSelection::Unchanged => {} ListSelection::Selected => { state.file_picker_pending_name = entry.as_path().into() } ListSelection::Activated => activated = true, } ctx.attr_overflow(Overflow::TruncateMiddle); } ctx.list_end(); if ctx.contains_focus() && ctx.consume_shortcut(vk::BACK) { state.file_picker_pending_name = "..".into(); activated = true; } } ctx.scrollarea_end(); if activated { doit = draw_file_picker_update_path(state); // Check if the file already exists and show an overwrite warning in that case. if state.wants_file_picker != StateFilePicker::Open && let Some(path) = doit.as_deref() && path.exists() { state.file_picker_overwrite_warning = doit.take(); } } } if ctx.modal_end() { done = true; } if state.file_picker_overwrite_warning.is_some() { let mut save; ctx.modal_begin("overwrite", loc(LocId::FileOverwriteWarning)); ctx.attr_background_rgba(ctx.indexed(IndexedColor::Red)); ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::BrightWhite)); { let contains_focus = ctx.contains_focus(); ctx.label("description", loc(LocId::FileOverwriteWarningDescription)); ctx.attr_overflow(Overflow::TruncateTail); ctx.attr_padding(Rect::three(1, 2, 1)); ctx.table_begin("choices"); ctx.inherit_focus(); ctx.attr_padding(Rect::three(0, 2, 1)); ctx.attr_position(Position::Center); ctx.table_set_cell_gap(Size { width: 2, height: 0 }); { ctx.table_next_row(); ctx.inherit_focus(); save = ctx.button("yes", loc(LocId::Yes), ButtonStyle::default()); ctx.inherit_focus(); if ctx.button("no", loc(LocId::No), ButtonStyle::default()) { state.file_picker_overwrite_warning = None; } } ctx.table_end(); if contains_focus { save |= ctx.consume_shortcut(vk::Y); if ctx.consume_shortcut(vk::N) { state.file_picker_overwrite_warning = None; } } } if ctx.modal_end() { state.file_picker_overwrite_warning = None; } if save { doit = state.file_picker_overwrite_warning.take(); } } if let Some(path) = doit { let res = if state.wants_file_picker == StateFilePicker::Open { state.documents.add_file_path(&path).map(|_| ()) } else if let Some(doc) = state.documents.active_mut() { doc.save(Some(path)) } else { Ok(()) }; match res { Ok(..) => { ctx.needs_rerender(); done = true; } Err(err) => error_log_add(ctx, state, err), } } if done { state.wants_file_picker = StateFilePicker::None; state.file_picker_pending_name = Default::default(); state.file_picker_entries = Default::default(); state.file_picker_overwrite_warning = Default::default(); } } // Returns Some(path) if the path refers to a file. fn draw_file_picker_update_path(state: &mut State) -> Option<PathBuf> { let old_path = state.file_picker_pending_dir.as_path(); let path = old_path.join(&state.file_picker_pending_name); let path = path::normalize(&path); let (dir, name) = if path.is_dir() { // If the current path is C:\ and the user selects "..", we want to // navigate to the drive picker. Since `path::normalize` will turn C:\.. into C:\, // we can detect this by checking if the length of the path didn't change. let dir = if cfg!(windows) && state.file_picker_pending_name == Path::new("..") // It's unnecessary to check the contents of the paths. && old_path.as_os_str().len() == path.as_os_str().len() { Path::new("") } else { path.as_path() }; (dir, PathBuf::new()) } else { let dir = path.parent().unwrap_or(&path); let name = path.file_name().map_or(Default::default(), |s| s.into()); (dir, name) }; if dir != state.file_picker_pending_dir.as_path() { state.file_picker_pending_dir = DisplayablePathBuf::from_path(dir.to_path_buf()); state.file_picker_entries = None; } state.file_picker_pending_name = name; if state.file_picker_pending_name.as_os_str().is_empty() { None } else { Some(path) } } fn draw_dialog_saveas_refresh_files(state: &mut State) { let dir = state.file_picker_pending_dir.as_path(); let mut files = Vec::new(); let mut off = 0; #[cfg(windows)] if dir.as_os_str().is_empty() { // If the path is empty, we are at the drive picker. // Add all drives as entries. for drive in edit::sys::drives() { files.push(DisplayablePathBuf::from_string(format!("{drive}:\\"))); } state.file_picker_entries = Some(files); return; } if cfg!(windows) || dir.parent().is_some() { files.push(DisplayablePathBuf::from("..")); off = 1; } if let Ok(iter) = fs::read_dir(dir) { for entry in iter.flatten() { if let Ok(metadata) = entry.metadata() { let mut name = entry.file_name(); if metadata.is_dir() || (metadata.is_symlink() && fs::metadata(entry.path()).is_ok_and(|m| m.is_dir())) { name.push("/"); } files.push(DisplayablePathBuf::from(name)); } } } // Sort directories first, then by name, case-insensitive. files[off..].sort_by(|a, b| { let a = a.as_bytes(); let b = b.as_bytes(); let a_is_dir = a.last() == Some(&b'/'); let b_is_dir = b.last() == Some(&b'/'); match b_is_dir.cmp(&a_is_dir) { Ordering::Equal => icu::compare_strings(a, b), other => other, } }); state.file_picker_entries = Some(files); }

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/mixelpixx/microsoft-edit-mcp'

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