Git Product home page Git Product logo

lapce-plugin-rust's Introduction

Lightning-fast And Powerful Code Editor


Lapce (IPA: /læps/) is written in pure Rust with a UI in Floem. It is designed with Rope Science from the Xi-Editor which makes for lightning-fast computation, and leverages Wgpu for rendering. More information about the features of Lapce can be found on the main website and user documentation can be found on GitBook.

Features

  • Built-in LSP (Language Server Protocol) support to give you intelligent code features such as: completion, diagnostics and code actions
  • Modal editing support as first class citizen (Vim-like, and toggleable)
  • Built-in remote development support inspired by VSCode Remote Development. Enjoy the benefits of a "local" experience, and seamlessly gain the full power of a remote system. We also have lapdev which can help manage your remote dev environments.
  • Plugins can be written in programming languages that can compile to the WASI format (C, Rust, AssemblyScript)
  • Built-in terminal, so you can execute commands in your workspace, without leaving Lapce.

Installation

You can find pre-built releases for Windows, Linux and macOS here, or installing with a package manager. If you'd like to compile from source, you can find the guide.

Contributing

Guidelines for contributing to Lapce can be found in CONTRIBUTING.md.

Feedback & Contact

The most popular place for Lapce developers and users is on the Discord server.

Or, join the discussion on Reddit where we are just getting started.

There is also a Matrix Space, which is linked to the content from the Discord server.

License

Lapce is released under the Apache License Version 2, which is an open source license. You may contribute to this project, or use the code as you please as long as you adhere to its conditions. You can find a copy of the license text here: LICENSE.

lapce-plugin-rust's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

lapce-plugin-rust's Issues

Make handling requests/notifications nicer

Currently LapcePlugin is defined as:

#[allow(unused_variables)]
pub trait LapcePlugin {
    fn handle_request(&mut self, id: u64, method: String, params: Value) {}
    fn handle_notification(&mut self, method: String, params: Value) {}
}

This is a bit eh in that it 'forces' the caller to do a big match statement over the method and then do the deserialization themselves. Ideally we would lessen that friction by default.

My initial thoughts are to have something vaguely like:
(I spell out types to make it more clear, but a lot of the callback types could be inferred)

// T is state.
// Q: A shorter/better name for `ResponseSender`?
type RequestHandler<T, V> = Box<dyn Fn(&mut T, V, ResponseSender)>;
type NotifHandler<T, V> = Box<dyn Fn(&mut T, V)>;
struct MsgHandler<T> {
    request_handlers: HashMap<String, RequestHandler<T, serde_json::Value>>,
    notif_handlers: HashMap<String, NotifHandler<T, serde_json::Value>>
}


impl<T> MsgHandler<T> {
    pub fn new() -> MsgHandler { /* ... */ }

    // Deserialize bound is a bit trickier in reality, but basically this
    // Q: The error `_` should probably be serde_json's deserialize error type but perhaps it should be more general?
    pub fn on_request<V: Deserialize, R: Serialize>(mut self, method: impl Into<String>, cb: impl Fn(&mut T, Result<V, _>, ResponseSender) + 'static) -> Self {
        // The outline of how request handlers work is that you're supposed to call the `send` function on `ResponseSender`` with your data.
        //   (We can't have it simply be returned from the function, because sometimes you only want to run LSP commands *after* you respond, like in initialize)
        let cb = Box::new(move |state: &mut T, data: serde_json::Value, resp: ResponseSender| {
            let data: Result<V, _> = serde_json::from_value(data);
            cb(state, data, resp);
        });
        self.request_handlers.insert(method.into(), cb);
    }

    pub fn on_notif<V: Deserialize>(mut self, method: impl Into<String>, cb: impl Fn(&mut T, Result<V, _>) + 'static) -> Self {
        let cb = Box::new(move |state: &mut T, data: serde_json::Value| {
            let data: Result<V, _> = serde_json::from_value(data);
            cb(state, data);
        });
        self.notif_handlers.insert(method.into(), cb);
    }

    /// Listen for the [`Initialize`] request.
    /// ... common docs here so that plugin authors don't have to read the lsp spec ...
    pub fn on_initialize(mut self, cb: impl Fn(&mut T, Result<InitializeParams, _>, ResponseSender) + 'static) -> Self {
        self.on_request(Initialize::Method, cb)
    }

    // ...
    // Various common functions.
    // We might not want to implement functions for literally every random LSP feature. Just common ones.
}

pub struct ResponseSender {
    // ... whatever is needed to send a response to the request ...
    // We could do FUN generic PhantomData to make so the closure can only send values of the allowed response type. I think that would be nice without adding complexity.

    // Q: we can log a warning if you didn't send a response/err back? Not sure if that's desirable in general.
    // sent_response: bool,
}
// ... obvious response sender impl ...

This would allow some common way of implementing a LSP startup to be like:

MsgHandler::new()
    .on_initialize(|state: &mut State, res: Result<InitializeParams, RpcError>, resp: ResponseSender| {
        resp.send(InitializeResult {
            // ...
        });

        PLUGIN_RPC.start_lsp(
            // ...
        );
    });

There's now two questions:

  • How do we say that we're using a specific message handler? There's no main function that the plugin can nicely 'start' in.
  • Do we want to allow dynamically registering handlers?
    • I lean towards yes, though do we alert the client editor about there being nothing handling the method?
      • RA logs warnings if it receives methods it doesn't handle. Maybe we should just automatically send out a command to log that, to encourage the plugin author to handle it nicely?

I'd say that what we can do is make the register_plugin have an alternate sort if you specify a 'handler creation function',

register_plugin!(State; handler: handler);

fn handler() -> MsgHandler<State> {
    MsgHandler::new()
        .on_initialize(|state: &mut State, res: Result<InitializeParams, RpcError>, resp: ResponseSender| {
            resp.send(InitializeResult {
                // ...
            });

            PLUGIN_RPC.start_lsp(
                // ...
            );
        })
}

We also introduce a new trait that acts as trait which LapcePlugin requires.

// ... inside lapce-plugin-rust
pub trait Handler {
    fn handle_request(&mut self, id: u64, method: String, params: Value);

    fn handle_notification(&mut self, method: String, params: Value);
}

pub trait LapcePlugin: Handler {}

The register_plugin trait would simply implement handler automatically.

// Example possible output of register plugin when specifying a handler function.
thread_local! {
    static STATE: std::cell::RefCell<State> = std::cell::RefCell::new(Default::default());
    static HANDLER: OnceCell<RefCell<MsgHandler<State>>> = OnceCell::new();
}

fn main() {}

pub fn handle_rpc() {
    // ... typical handle_rpc ...
}

impl Handler for State {
    fn handle_request(&mut self, id: u64, method: String, params: Value) {
        STATE.with(move |state| {
            HANDLER.get_or_init(|| RefCell::new(handler())).borrow_mut().handle_request(state, id, method, params)
        })
    }

    fn handle_notification(&mut self, method: String, params: Value) {
        STATE.with(move |state| {
            HANDLER.get_or_init(|| RefCell::new(handler())).borrow_mut().handle_notification(state, method, params)
        })
    }
}

This would allow the existing plugin implementations to just automatically work without any changes, and would allow swapping it out for a completely custom handler if desired.

Function to send notification to client editor

There should be a function to easily send a notification to the client from a plugin. This can currently be achieved via object_to_stdout, but in a somewhat unpleasant manner and makes the plugin authors have to figure out the details of the LSP/PSP api for sending information. (I think that function should also be hidden, and plugin authors using the library should just deal with functions for sending notifications/requests)
We can use lsp-types/psp-types Notification trait to make things more type-safe, like:

pub fn send_notification<T: Notification>(params: T::Params) {
    // Having them give the `T` ensures that they can't give us the wrong method name or the wrong params
    // They can still do custom notifications (like editor-specific notifs) via making a custom structure and implementing Notification on it
    let method: &'static str = T::METHOD;
    // We could just have send_host_notification take `impl Serialize`?
    send_host_notification(method, json!(params));
}

Constant high CPU usage on Rust projects when window is focused

System

Lapce version 0.2.0, installed from ArchLinux package manager. Up-to-date ArchLinux.

Steps to reproduce

  • Open a non-Rust project (directory), such as a directory with just text files
  • Observe normal CPU usage (as reported by e.g. htop)
  • Open a Rust project
  • Observe the CPU usage rising to ~40% of constant usage of one core if the window focus is on Lapce
  • Switch focus to another desktop (Linux Xorg terminology)
  • Observe the CPU dropping to ~0%

In short, having Lapce window focused while on a Rust project causes constant ~40% CPU usage of 1 core, which drops to 0% on non-Rust projects and for when the wendow is not visible

Additional information

No idea honestly how to explain that :(
Just documented what I see. Would be happy with debugging if needed! I'm trying out Lapce periodically and I know and use Rust myself

  • The process eating the CPU is lapce, as reported by htop.

  • Potentially related to lapce/lapce#461, but this issue is present always, not just on start-up.

  • Potentially related to lapce/lapce#503, but this issue is more specific (Rust and active window focus).

  • Potentially related to lapce/lapce#1043, but this issue is more specific.

Where are some examples?

I want to develop a plugin for php, but I don't know how to get started. Hope to have a guide to refer to

How to write a plugin?

I participated in the internal test of a brand new language. I want to adapt lapce for this language. How should I add syntax highlighting and access the lsp of this language?

how to debug plugin?

I'm writing a plugin for php, but it doesn't work. The only feedback I got from the console was

parse error expected value at line 1 column 1

Is there anyone who can help me?

Async Requests

It would be good to have the ability to send asynchronous requests, and handle the result via a callback.
Ex: Sending a command to a spawned LSP server shouldn't typically need to block the plugin. It can typically handle multiple requests (like multiple commands being ran) that it then can immediately send to the likely-multithreaded LSP for processing.

This would probably require modifying handle rpc to take into account responses. Possibly having some sort of HashMap<Id, Callback> store on PluginServerRpcHandler.

(Or we could use actual Rust async but I'm unsure of the nicest way to fit that into this. Also we currently have STATE be a thread local, etc.)

Hide pieces of the API

There's various pieces of the API that should probably be hidden or made non-constructable.
Hiding functions would allow us to later replace or alter the code behind them without breaking 'good' plugins because they shouldn't be using them directly. Making some structures non-constructable would let us add fields in the future easily.

  • handle_rpc is an internal function, it should be #[doc(hidden)].
  • Ideally users should never have to use object_from_stdin/object_from_stdout. These should probably be internal, and we just pass the requests/notifications to the plugin.
  • parse_stdin should be hidden
  • Http should be made non-constructable (add a hidden empty marker field), just in case.
  • VoltEnvironment should be made non-constructable.
    • Really I think VoltEnvironment should have to be created if you want to use it.
    • Would let us cache values in the future.
  • PluginError should perhaps be #[non_exhaustive] in case we want to add other error kinds with types in the future.

Functions to send requests and receive response from client editor

A nice way, like #8, for sending requests is desirable.
However, we run into the issue that the plugin author needs to handle any responses from the editor that they receive.
One method would be this:

fn send_request<T: Request>(params: T::Params) -> u64 {
    // do some stuff for incrementing a held id, like pluginrpc does currently
    // send request
    // return id
}

Then, the plugin author would just hold that id and check it against whatever they receive in handle_response which gives you the id... but there doesn't seem to be a handle response yet?

That's one way of doing it, and does allow the plugin author to decide how to handle it.


Another potential way, which could be just be implemented on top of the previous method as a library or helper function, is callbacks. This is sort of similar to what Lapce uses internally for responses from what I remember.

type RequestCallback<T: Request> = Box<dyn FnOnce(Result<T::Result, Error>)>;
fn send_request_cb<T: Request>(params: T::Params, response_cb: impl FnOnce<Result<T::Result, Error>>) {
    // Wrapper callback which takes in unparsed value and parses it before giving it to the callback
    let parser_resp_cb = Box::new(|params: Result<Value, Error>| {
        let params: Result<T::Result, Error> = match params {
            Ok(params) => serde_json::from_value::<T::Result>(params),
            Err(err) => Err(err),
        };
     
        response_cb(params);
    });
    // store parser_resp_cb in some `HashMap<id, Box<dyn FnOnce(Result<Value, Error>)>`, since we wanted to store it on here we had to make it non-generic
}

Then, when the code receives a response, it looks up the id in the hashmap and removes the function if it exists. Then it calls the function with the response value (or an error if the response was an error).
I think we should provide &mut State as a parameter so that they can modify their state when they get the response, rather than relying on more annoying methods of synchronization. However, we don't know the type of their State until later. We could put these functions to be generated by the register_plugin macro.


I think this second option is probably what the default should be. Though we could have both, so then if they want to keep track of their callbacks in a different manner then they can.

Function to respond to a request from the client editor

Currently, one can respond to a request (received in handle_request) by using object_to_stdout, but that is a poor API. Here's my idea of the function definition for responding:

/// `T` is the type of the request you're responding to
fn send_response<T: Request>(id: u64, params: T::Result);

So, knowing the request type (which they can presumably do), it enforces that the resulting type you send back is correct.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.