Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Proc macro ABI

extern crate proc_macro;

#[allow(dead_code)]
#[doc(hidden)]
mod ra_server;

use libloading::Library;
use proc_macro_api::ProcMacroKind;

use super::PanicMessage;

pub(crate) struct Abi {
    exported_macros: Vec<proc_macro::bridge::client::ProcMacro>,
}

impl From<proc_macro::bridge::PanicMessage> for PanicMessage {
    fn from(p: proc_macro::bridge::PanicMessage) -> Self {
        Self { message: p.as_str().map(|s| s.to_string()) }
    }
}

impl Abi {
    pub unsafe fn from_lib(lib: &Library, symbol_name: String) -> Result<Abi, libloading::Error> {
        let macros: libloading::Symbol<'_, &&[proc_macro::bridge::client::ProcMacro]> =
            lib.get(symbol_name.as_bytes())?;
        Ok(Self { exported_macros: macros.to_vec() })
    }

    pub fn expand(
        &self,
        macro_name: &str,
        macro_body: &tt::Subtree,
        attributes: Option<&tt::Subtree>,
    ) -> Result<tt::Subtree, PanicMessage> {
        let parsed_body = ra_server::TokenStream::with_subtree(macro_body.clone());

        let parsed_attributes = attributes.map_or(ra_server::TokenStream::new(), |attr| {
            ra_server::TokenStream::with_subtree(attr.clone())
        });

        for proc_macro in &self.exported_macros {
            match proc_macro {
                proc_macro::bridge::client::ProcMacro::CustomDerive {
                    trait_name, client, ..
                } if *trait_name == macro_name => {
                    let res = client.run(
                        &proc_macro::bridge::server::SameThread,
                        ra_server::RustAnalyzer::default(),
                        parsed_body,
                        true,
                    );
                    return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                }
                proc_macro::bridge::client::ProcMacro::Bang { name, client }
                    if *name == macro_name =>
                {
                    let res = client.run(
                        &proc_macro::bridge::server::SameThread,
                        ra_server::RustAnalyzer::default(),
                        parsed_body,
                        true,
                    );
                    return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                }
                proc_macro::bridge::client::ProcMacro::Attr { name, client }
                    if *name == macro_name =>
                {
                    let res = client.run(
                        &proc_macro::bridge::server::SameThread,
                        ra_server::RustAnalyzer::default(),
                        parsed_attributes,
                        parsed_body,
                        true,
                    );
                    return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                }
                _ => continue,
            }
        }

        Err(proc_macro::bridge::PanicMessage::String("Nothing to expand".to_string()).into())
    }

    pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
        self.exported_macros
            .iter()
            .map(|proc_macro| match proc_macro {
                proc_macro::bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
                    (trait_name.to_string(), ProcMacroKind::CustomDerive)
                }
                proc_macro::bridge::client::ProcMacro::Bang { name, .. } => {
                    (name.to_string(), ProcMacroKind::FuncLike)
                }
                proc_macro::bridge::client::ProcMacro::Attr { name, .. } => {
                    (name.to_string(), ProcMacroKind::Attr)
                }
            })
            .collect()
    }
}
( Some(":wqa<ret>"), Some(&|app| { helpers::assert_status_not_error(&app.editor); assert_eq!(0, app.editor.tree.views().count()); }), ), ], true, ) .await?; helpers::assert_file_has_content(&mut file1, &LineFeedHandling::Native.apply("hello1"))?; helpers::assert_file_has_content(&mut file2, &LineFeedHandling::Native.apply("hello2"))?; helpers::assert_file_has_content(&mut file3, &LineFeedHandling::Native.apply("hello3"))?; Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn test_split_write_quit_same_file() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; let mut app = helpers::AppBuilder::new() .with_file(file.path(), None) .build()?; test_key_sequences( &mut app, vec![ ( Some("O<esc>ihello<esc>:sp<ret>ogoodbye<esc>"), Some(&|app| { assert_eq!(2, app.editor.tree.views().count()); helpers::assert_status_not_error(&app.editor); let mut docs: Vec<_> = app.editor.documents().collect(); assert_eq!(1, docs.len()); let doc = docs.pop().unwrap(); assert_eq!( LineFeedHandling::Native.apply("hello\ngoodbye"), doc.text().to_string() ); assert!(doc.is_modified()); }), ), ( Some(":wq<ret>"), Some(&|app| { helpers::assert_status_not_error(&app.editor); assert_eq!(1, app.editor.tree.views().count()); let mut docs: Vec<_> = app.editor.documents().collect(); assert_eq!(1, docs.len()); let doc = docs.pop().unwrap(); assert_eq!( LineFeedHandling::Native.apply("hello\ngoodbye"), doc.text().to_string() ); assert!(!doc.is_modified()); }), ), ], false, ) .await?; helpers::assert_file_has_content(&mut file, &LineFeedHandling::Native.apply("hello\ngoodbye"))?; Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn test_changes_in_splits_apply_to_all_views() -> anyhow::Result<()> { // See <https://github.com/helix-editor/helix/issues/4732>. // Transactions must be applied to any view that has the changed document open. // This sequence would panic since the jumplist entry would be modified in one // window but not the other. Attempting to update the changelist in the other // window would cause a panic since it would point outside of the document. // The key sequence here: // * <C-w>v Create a vertical split of the current buffer. // Both views look at the same doc. // * [<space> Add a line ending to the beginning of the document. // The cursor is now at line 2 in window 2. // * <C-s> Save that selection to the jumplist in window 2. // * <C-w>w Switch to window 1. // * kd Delete line 1 in window 1. // * <C-w>q Close window 1, focusing window 2. // * d Delete line 1 in window 2. // // This panicked in the past because the jumplist entry on line 2 of window 2 // was not updated and after the `kd` step, pointed outside of the document. test(( "#[|]#", "<C-w>v[<space><C-s><C-w>wkd<C-w>qd", "#[|]#", LineFeedHandling::AsIs, )) .await?; // Transactions are applied to the views for windows lazily when they are focused. // This case panics if the transactions and inversions are not applied in the // correct order as we switch between windows. test(( "#[|]#", "[<space>[<space>[<space><C-w>vuuu<C-w>wUUU<C-w>quuu", "#[|]#", LineFeedHandling::AsIs, )) .await?; // See <https://github.com/helix-editor/helix/issues/4957>. // This sequence undoes part of the history and then adds new changes, creating a // new branch in the history tree. `View::sync_changes` applies transactions down // and up to the lowest common ancestor in the path between old and new revision // numbers. If we apply these up/down transactions in the wrong order, this case // panics. // The key sequence: // * 3[<space> Create three empty lines so we are at the end of the document. // * <C-w>v<C-s> Create a split and save that point at the end of the document // in the jumplist. // * <C-w>w Switch back to the first window. // * uu Undo twice (not three times which would bring us back to the // root of the tree). // * 3[<space> Create three empty lines. Now the end of the document is past // where it was on step 1. // * <C-w>q Close window 1, focusing window 2 and causing a sync. This step // panics if we don't apply in the right order. // * %d Clean up the buffer. test(( "#[|]#", "3[<space><C-w>v<C-s><C-w>wuu3[<space><C-w>q%d", "#[|]#", LineFeedHandling::AsIs, )) .await?; Ok(()) }