дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
feat(editor): paste and drop images into Markdown files (#489)
Pasting or dropping an image into a Markdown editor saves it beside the document in `assets/` and inserts a URL-encoded relative link as one undo step, in local and remote projects. Existing names get numbered suffixes; the folder and clipboard name pattern are in Settings › Editor › General. Non-Markdown editors, text paste and other dropped files keep their behavior. Fixes #308
Цей коміт міститься в:
1 джерело
f592ff5b05
коміт
df6ed5ccba
18 files changed
+498
-2
No files matched your search
@@ -12,6 +12,8 @@ use std::collections::BTreeMap;
|
||||
pub struct EditorGeneralSettings {
|
||||
/// Change font size with ⌘ + mouse wheel.
|
||||
pub wheel_zoom: bool,
|
||||
pub markdown_image_folder: String,
|
||||
pub markdown_image_name_pattern: String,
|
||||
/// Drag-and-drop of selected text inside the editor.
|
||||
pub drag_and_drop: bool,
|
||||
/// When the caret moves out of view: `minimize` scrolling | `center` the caret line.
|
||||
@@ -35,6 +37,8 @@ impl Default for EditorGeneralSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wheel_zoom: false,
|
||||
markdown_image_folder: "assets".into(),
|
||||
markdown_image_name_pattern: "image-{timestamp}".into(),
|
||||
drag_and_drop: true,
|
||||
caret_scroll: "minimize".into(),
|
||||
soft_wrap_files: "*.md; *.txt; *.rst; *.adoc".into(),
|
||||
|
||||
@@ -49,6 +49,10 @@ pub fn create_file(host: &Host, window: &str, path: PathBuf) -> IdeResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_file_bytes(host: &Host, path: PathBuf, data: Vec<u8>) -> IdeResult<()> {
|
||||
host.blocking(move || ide_vfs::create_file_bytes(&path, &data)).await
|
||||
}
|
||||
|
||||
/// Toggle Read-only Attribute: sets the writable bit (and the open document's
|
||||
/// state). Returns whether the file is read-only now.
|
||||
pub fn set_writable(host: &Host, window: &str, path: PathBuf, writable: bool) -> IdeResult<bool> {
|
||||
|
||||
@@ -168,6 +168,9 @@ impl Dispatcher {
|
||||
.add("cancel_task", |c| async move { Ok(ide_core::task::cancel(c.arg::<TaskId>("id")?)) })
|
||||
.add("fs_read_dir", |c| async move { ops::fs::read_dir(&c.host, &c.window, c.arg("path")?) })
|
||||
.add("fs_create_file", |c| async move { ops::fs::create_file(&c.host, &c.window, c.arg("path")?) })
|
||||
.add("fs_create_file_bytes", |c| async move {
|
||||
ops::fs::create_file_bytes(&c.host, c.arg("path")?, c.arg("data")?).await
|
||||
})
|
||||
.add("fs_create_dir", |c| async move { ide_vfs::create_dir(&c.arg::<PathBuf>("path")?) })
|
||||
.add("fs_rename", |c| async move { ide_vfs::rename(&c.arg::<PathBuf>("from")?, &c.arg::<PathBuf>("to")?) })
|
||||
.add("fs_set_writable", |c| async move {
|
||||
|
||||
@@ -232,6 +232,12 @@ pub fn save_text(path: &Path, text: &str, encoding: &str, has_bom: bool) -> IdeR
|
||||
pub fn create_file(path: &Path) -> IdeResult<()> {
|
||||
ops::create_file(path)
|
||||
}
|
||||
|
||||
/// Creates a binary file exclusively, including missing parent directories.
|
||||
pub fn create_file_bytes(path: &Path, data: &[u8]) -> IdeResult<()> {
|
||||
ops::create_file_bytes(path, data)
|
||||
}
|
||||
|
||||
/// Creates a directory and missing parents; `AlreadyExists` if present.
|
||||
pub fn create_dir(path: &Path) -> IdeResult<()> {
|
||||
ops::create_dir(path)
|
||||
|
||||
@@ -35,6 +35,17 @@ pub(crate) fn create_file(path: &Path) -> IdeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn create_file_bytes(path: &Path, data: &[u8]) -> IdeResult<()> {
|
||||
use std::io::Write;
|
||||
if exists(path) {
|
||||
return Err(already_exists(path));
|
||||
}
|
||||
ensure_parent(path)?;
|
||||
let mut file = fs::OpenOptions::new().write(true).create_new(true).open(path)?;
|
||||
file.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// IDEA's "Toggle Read-only Attribute": read-only clears every write bit,
|
||||
/// writable adds the owner's.
|
||||
pub(crate) fn set_writable(path: &Path, writable: bool) -> IdeResult<()> {
|
||||
|
||||
@@ -367,3 +367,34 @@ fn worktree_and_nested_repository_local_excludes_are_reflected_in_listings() {
|
||||
let entries = ide_vfs::read_dir(&nested, &[]).unwrap();
|
||||
assert!(entries.iter().find(|e| e.name == "private").unwrap().ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_file_bytes_is_exclusive() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("image.png");
|
||||
let bytes = [0, 255, 128, 10];
|
||||
ide_vfs::create_file_bytes(&path, &bytes).unwrap();
|
||||
let error = ide_vfs::create_file_bytes(&path, b"replacement").unwrap_err();
|
||||
assert_eq!(error.kind, ide_core::error::ErrorKind::AlreadyExists);
|
||||
assert_eq!(std::fs::read(&path).unwrap(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_file_bytes_creates_parents() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("nested/assets/image.png");
|
||||
ide_vfs::create_file_bytes(&path, &[0, 255]).unwrap();
|
||||
assert_eq!(std::fs::read(path).unwrap(), [0, 255]);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn create_file_bytes_does_not_follow_existing_symlink() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let target = dir.path().join("target.png");
|
||||
let link = dir.path().join("link.png");
|
||||
std::os::unix::fs::symlink(&target, &link).unwrap();
|
||||
let error = ide_vfs::create_file_bytes(&link, b"data").unwrap_err();
|
||||
assert_eq!(error.kind, ide_core::error::ErrorKind::AlreadyExists);
|
||||
assert!(!target.exists());
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача