Skip to content
This repository has been archived by the owner on Jun 15, 2021. It is now read-only.

Commit

Permalink
Add the plugin system
Browse files Browse the repository at this point in the history
  • Loading branch information
Peltoche committed Mar 21, 2019
1 parent af56dc1 commit 3a3a0c5
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
/target
**/*.rs.bk
/out
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions Cargo.toml
Expand Up @@ -23,17 +23,17 @@ serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

[dependencies.xi-rpc]
git = "https://github.com/xi-editor/xi-editor"
branch = "master"
[dependencies.clap]
features = ["suggestions", "color", "wrap_help"]
version = "2.32.0"

[dependencies.xi-core-lib]
git = "https://github.com/xi-editor/xi-editor"
branch = "master"
git = "https://github.com/xi-editor/xi-editor"

[dependencies.clap]
features = ["suggestions", "color", "wrap_help"]
version = "2.32.0"
[dependencies.xi-rpc]
branch = "master"
git = "https://github.com/xi-editor/xi-editor"

[workspace]
members = ["./plugins/*"]
22 changes: 22 additions & 0 deletions plugins/test/Cargo.toml
@@ -0,0 +1,22 @@
[package]
authors = ["Peltoche <dev@halium.fr>"]
edition = "2018"
name = "xi-test-plugin"
version = "0.1.0"
workspace = "./../.."

[dependencies]
serde = "1.0"
serde_derive = "1.0"

[dependencies.xi-core-lib]
git = "https://github.com/xi-editor/xi-editor"
branch = "master"

[dependencies.xi-rope]
git = "https://github.com/xi-editor/xi-editor"
branch = "master"

[dependencies.xi-plugin-lib]
git = "https://github.com/xi-editor/xi-editor"
branch = "master"
34 changes: 34 additions & 0 deletions plugins/test/Makefile
@@ -0,0 +1,34 @@
# Makefile for installing the plugin on macOS and Linux

# The 'official' name of this plugin, displayed in menus etc
PLUGIN_NAME = test-plugin
# the name of the plugin binary; this is the same as the name in Cargo.toml
PLUGIN_BIN = xi-test-plugin

# On MacOS we just always assume that plugins are in the default location
ifeq ($(shell uname -s), Darwin)
XI_CONFIG_DIR ?= $(HOME)/Library/Application\ Support/XiEditor
endif

XDG_CONFIG_HOME ?= $(HOME)/.config
XI_CONFIG_DIR ?= $(XDG_CONFIG_HOME)/xi
XI_PLUGIN_DIR ?= $(XI_CONFIG_DIR)/plugins

out/$(PLUGIN_NAME): $(PLUGIN_BIN)
mkdir -p out/$(PLUGIN_NAME)/bin
cp ./../../target/release/$(PLUGIN_BIN) out/$(PLUGIN_NAME)/bin
cp manifest.toml out/$(PLUGIN_NAME)/manifest.toml

.PHONY: $(PLUGIN_BIN)
$(PLUGIN_BIN):
cargo build --release

install: manifest.toml out/$(PLUGIN_NAME)
mkdir -p $(XI_PLUGIN_DIR)
cp -r out/$(PLUGIN_NAME) $(XI_PLUGIN_DIR)

clean:
rm -rf out
cargo clean

.PHONY: clean install
22 changes: 22 additions & 0 deletions plugins/test/README.md
@@ -0,0 +1,22 @@
# Sample Rust Plugin

This repository contains a very, very barebones rust plugin, including
a manifest and a Makefile.

This is intended as a template for people interested in writing a rust plugin.

## Installation

tldr; `make install`.

To install this plugin, the plugin manifest must be placed in a new directory under
$XI_CONFIG_DIR/plugins, where $XI_CONFIG_DIR is the path passed by your client
to xi core via the `client_started` RPC's `config_dir` field, on startup.
On MacOS, by default, $XI_CONFIG_DIR is located at ~/Library/Application Support/XiEditor.

Additionally, the compiled binary should be placed in a `bin/` subdir of the
directory containing the manifest. (This is the default; the location can be
changed in the manifest.)



5 changes: 5 additions & 0 deletions plugins/test/manifest.toml
@@ -0,0 +1,5 @@
# The plugin manifest describes the plugin and its capabilities.
# At the very least it must contain these three fields:
name = "test-plugin"
version = "0.0"
exec_path = "./bin/xi-test-plugin"
Binary file added plugins/test/out/test-plugin/bin/xi-test-plugin
Binary file not shown.
5 changes: 5 additions & 0 deletions plugins/test/out/test-plugin/manifest.toml
@@ -0,0 +1,5 @@
# The plugin manifest describes the plugin and its capabilities.
# At the very least it must contain these three fields:
name = "test-plugin"
version = "0.0"
exec_path = "./bin/xi-test-plugin"
112 changes: 112 additions & 0 deletions plugins/test/src/main.rs
@@ -0,0 +1,112 @@
// Copyright 2016 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A sample plugin, intended as an illustration and a template for plugin
//! developers.
extern crate xi_core_lib;
extern crate xi_plugin_lib;
extern crate xi_rope;

use std::path::Path;

use crate::xi_core_lib::ConfigTable;
use xi_plugin_lib::{mainloop, ChunkCache, Error, Plugin, View};
use xi_rope::delta::Builder as EditBuilder;
use xi_rope::interval::Interval;
use xi_rope::rope::RopeDelta;

/// A type that implements the `Plugin` trait, and interacts with xi-core.
///
/// Currently, this plugin has a single noteworthy behaviour,
/// intended to demonstrate how to edit a document; when the plugin is active,
/// and the user inserts an exclamation mark, the plugin will capitalize the
/// preceding word.
struct SamplePlugin;

//NOTE: implementing the `Plugin` trait is the sole requirement of a plugin.
// For more documentation, see `rust/plugin-lib` in this repo.
impl Plugin for SamplePlugin {
type Cache = ChunkCache;

fn new_view(&mut self, view: &mut View<Self::Cache>) {
eprintln!("new view {}", view.get_id());
}

fn did_close(&mut self, view: &View<Self::Cache>) {
eprintln!("close view {}", view.get_id());
}

fn did_save(&mut self, view: &mut View<Self::Cache>, _old: Option<&Path>) {
eprintln!("saved view {}", view.get_id());
}

fn config_changed(&mut self, _view: &mut View<Self::Cache>, _changes: &ConfigTable) {}

fn update(
&mut self,
view: &mut View<Self::Cache>,
delta: Option<&RopeDelta>,
_edit_type: String,
_author: String,
) {
//NOTE: example simple conditional edit. If this delta is
//an insert of a single '!', we capitalize the preceding word.
if let Some(delta) = delta {
let (iv, _) = delta.summary();
let text: String = delta
.as_simple_insert()
.map(String::from)
.unwrap_or_default();
if text == "!" {
let _ = self.capitalize_word(view, iv.end());
}
}
}
}

impl SamplePlugin {
/// Uppercases the word preceding `end_offset`.
fn capitalize_word(&self, view: &mut View<ChunkCache>, end_offset: usize) -> Result<(), Error> {
//NOTE: this makes it clear to me that we need a better API for edits
let line_nb = view.line_of_offset(end_offset)?;
let line_start = view.offset_of_line(line_nb)?;

let mut cur_utf8_ix = 0;
let mut word_start = 0;
for c in view.get_line(line_nb)?.chars() {
if c.is_whitespace() {
word_start = cur_utf8_ix;
}

cur_utf8_ix += c.len_utf8();

if line_start + cur_utf8_ix == end_offset {
break;
}
}

let new_text = view.get_line(line_nb)?[word_start..end_offset - line_start].to_uppercase();
let buf_size = view.get_buf_size();
let mut builder = EditBuilder::new(buf_size);
let iv = Interval::new(line_start + word_start, end_offset);
builder.replace(iv, new_text.into());
view.edit(builder.build(), 0, false, true, "sample".into());
Ok(())
}
}

fn main() {
let mut plugin = SamplePlugin;
mainloop(&mut plugin).unwrap();
}
29 changes: 27 additions & 2 deletions src/controller.rs
@@ -1,4 +1,5 @@
use ncurses::*;
use std::char;
use xi_rpc::Peer;

#[derive(Default)]
Expand All @@ -10,7 +11,15 @@ pub struct Controller {

impl Controller {
pub fn open_file(&mut self, core: Box<dyn Peer>, file_path: &str) {
core.send_rpc_notification("client_started", &json!({}));
let mut xi_config_dir = dirs::config_dir().expect("failed to retrieve your config dir");
xi_config_dir.push("xi");

core.send_rpc_notification(
"client_started",
&json!({
"config_dir": xi_config_dir.to_str().unwrap(),
}),
);

let view_id = core
.send_rpc_request("new_view", &json!({ "file_path": file_path }))
Expand Down Expand Up @@ -52,7 +61,8 @@ impl Controller {

pub fn start_keyboard_event_loop(&self, core: Box<dyn Peer>) {
loop {
match getch() {
let ch = getch();
match ch {
KEY_F1 => break,
KEY_UP => {
core.send_rpc_notification(
Expand Down Expand Up @@ -80,6 +90,21 @@ impl Controller {
}
_ => (),
}

match char::from_u32(ch as u32).expect("Invalid char") {
'i' => {
core.send_rpc_notification(
"edit",
&json!({
"method": "insert",
"params": {
"chars": "!",
},
"view_id": self.view_id}),
);
}
_ => (),
}
}
}
}
2 changes: 1 addition & 1 deletion src/event_handler.rs
Expand Up @@ -16,7 +16,7 @@ impl xi_rpc::Handler for EventHandler {

fn handle_notification(&mut self, ctx: &RpcCtx, rpc: Self::Notification) {
match rpc.method.as_str() {
"available_languages" => debug!("{}", &rpc.method),
"available_languages" => debug!("{} -> {}", &rpc.method, &rpc.params),
"available_themes" => debug!("{}", &rpc.method),
"available_plugins" => debug!("{} -> {}", &rpc.method, &rpc.params),
"config_changed" => debug!("{}", &rpc.method),
Expand Down

0 comments on commit 3a3a0c5

Please sign in to comment.