xi-core-lib 0.3.0

Library module for xi-core
Documentation
// Copyright 2017 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.

#[macro_use]
extern crate serde_json;

extern crate xi_core_lib;
extern crate xi_rpc;

use std::io;

use xi_core_lib::test_helpers;
use xi_core_lib::XiCore;
use xi_rpc::test_utils::{make_reader, test_channel};
use xi_rpc::{ReadError, RpcLoop};

#[test]
/// Tests that the handler responds to a standard startup sequence as expected.
fn test_startup() {
    let mut state = XiCore::new();
    let (tx, mut rx) = test_channel();
    let mut rpc_looper = RpcLoop::new(tx);
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}"#,
    );
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());
    rx.expect_rpc("available_languages");
    rx.expect_rpc("available_themes");
    rx.expect_rpc("theme_changed");

    let json = make_reader(r#"{"id":0,"method":"new_view","params":{}}"#);
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());
    assert_eq!(rx.expect_response(), Ok(json!("view-id-1")));
    rx.expect_rpc("available_plugins");
    rx.expect_rpc("config_changed");
    rx.expect_rpc("language_changed");
    rx.expect_rpc("update");
    rx.expect_rpc("scroll_to");
    rx.expect_nothing();
}

#[test]
/// Tests that the handler creates and destroys views and buffers
fn test_state() {
    let mut state = XiCore::new();

    let write = io::sink();
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}
{"id":0,"method":"new_view","params":{}}"#,
    );
    let mut rpc_looper = RpcLoop::new(write);
    rpc_looper.mainloop(|| json, &mut state).unwrap();

    {
        let state = state.inner();
        assert_eq!(state._test_open_editors(), vec![test_helpers::new_buffer_id(2)]);
        assert_eq!(state._test_open_views(), vec![test_helpers::new_view_id(1)]);
    }

    let json = make_reader(r#"{"method":"close_view","params":{"view_id":"view-id-1"}}"#);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
    {
        let state = state.inner();
        assert_eq!(state._test_open_views(), Vec::new());
        assert_eq!(state._test_open_editors(), Vec::new());
    }

    let json = make_reader(
        r#"{"id":1,"method":"new_view","params":{}}
{"id":2,"method":"new_view","params":{}}
{"id":3,"method":"new_view","params":{}}"#,
    );

    rpc_looper.mainloop(|| json, &mut state).unwrap();
    {
        let state = state.inner();
        assert_eq!(state._test_open_editors().len(), 3);
    }
}

#[test]
/// Tests that the runloop exits with the correct error when receiving
/// malformed json.
fn test_malformed_json() {
    let mut state = XiCore::new();
    let write = io::sink();
    let mut rpc_looper = RpcLoop::new(write);
    // malformed json: method should be in quotes.
    let read = make_reader(
        r#"{"method":"client_started","params":{}}
{"id":0,method:"new_view","params":{}}"#,
    );
    match rpc_looper.mainloop(|| read, &mut state).err().expect("malformed json exits with error") {
        ReadError::Json(_) => (), // expected
        err => panic!("Unexpected error: {:?}", err),
    }
    // read should have ended after first item
    {
        let state = state.inner();
        assert_eq!(state._test_open_editors().len(), 0);
    }
}

#[test]
/// Sends all of the cursor movement-related commands, and verifies that
/// they are handled.
///
///
/// Note: this is a test of message parsing, not of editor behaviour.
fn test_movement_cmds() {
    let mut state = XiCore::new();
    let write = io::sink();
    let mut rpc_looper = RpcLoop::new(write);
    // init a new view
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}
{"id":0,"method":"new_view","params":{}}"#,
    );
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());

    let json = make_reader(MOVEMENT_RPCS);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
}

#[test]
/// Sends all the commands which modify the buffer, and verifies that they
/// are handled.
fn test_text_commands() {
    let mut state = XiCore::new();
    let write = io::sink();
    let mut rpc_looper = RpcLoop::new(write);
    // init a new view
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}
{"id":0,"method":"new_view","params":{}}"#,
    );
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());

    let json = make_reader(TEXT_EDIT_RPCS);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
}

#[test]
fn test_other_edit_commands() {
    let mut state = XiCore::new();
    let write = io::sink();
    let mut rpc_looper = RpcLoop::new(write);
    // init a new view
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}
{"id":0,"method":"new_view","params":{}}"#,
    );
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());

    let json = make_reader(OTHER_EDIT_RPCS);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
}

#[test]
fn test_settings_commands() {
    let mut state = XiCore::new();
    let (tx, mut rx) = test_channel();
    let mut rpc_looper = RpcLoop::new(tx);
    // init a new view
    let json = make_reader(
        r#"{"method":"client_started","params":{}}
{"method":"set_theme","params":{"theme_name":"InspiredGitHub"}}
{"id":0,"method":"new_view","params":{}}"#,
    );
    assert!(rpc_looper.mainloop(|| json, &mut state).is_ok());
    rx.expect_rpc("available_languages");
    rx.expect_rpc("available_themes");
    rx.expect_rpc("theme_changed");
    rx.expect_response().unwrap();
    rx.expect_rpc("available_plugins");
    rx.expect_rpc("config_changed");
    rx.expect_rpc("language_changed");
    rx.expect_rpc("update");
    rx.expect_rpc("scroll_to");

    let json = make_reader(r#"{"method":"get_config","id":1,"params":{"view_id":"view-id-1"}}"#);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
    let resp = rx.expect_response().unwrap();
    assert_eq!(resp["tab_size"], json!(4));

    let json = make_reader(r#"{"method":"modify_user_config","params":{"domain":{"user_override":"view-id-1"},"changes":{"font_face": "Comic Sans"}}}
{"method":"modify_user_config","params":{"domain":{"syntax":"rust"},"changes":{"font_size":42}}}
{"method":"modify_user_config","params":{"domain":"general","changes":{"tab_size":13,"font_face":"Papyrus"}}}"#);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
    // discard config_changed
    rx.expect_rpc("config_changed");
    rx.expect_rpc("config_changed");

    let json = make_reader(r#"{"method":"get_config","id":2,"params":{"view_id":"view-id-1"}}"#);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
    let resp = rx.expect_response().unwrap();
    assert_eq!(resp["tab_size"], json!(13));
    assert_eq!(resp["font_face"], json!("Comic Sans"));

    // null value should clear entry from this config
    let json = make_reader(r#"{"method":"modify_user_config","params":{"domain":{"user_override":"view-id-1"},"changes":{"font_face": null}}}"#);
    rpc_looper.mainloop(|| json, &mut state).unwrap();
    let resp = rx.expect_rpc("config_changed");
    assert_eq!(resp.0["params"]["changes"]["font_face"], json!("Papyrus"));
}

//TODO: test saving rpc
//TODO: test plugin rpc

const MOVEMENT_RPCS: &str = r#"{"method":"edit","params":{"view_id":"view-id-1","method":"move_up","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_down","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_up_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_down_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_left","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_backward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_right","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_forward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_left_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_right_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_word_left","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_word_right","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_word_left_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_word_right_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_beginning_of_paragraph","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_end_of_paragraph","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_left_end_of_line","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_left_end_of_line_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_right_end_of_line","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_right_end_of_line_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_beginning_of_document","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_beginning_of_document_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_end_of_document","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"move_to_end_of_document_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"scroll_page_up","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"scroll_page_down","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"page_up_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"page_down_and_modify_selection","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"select_all","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"add_selection_above","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"add_selection_below","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"collapse_selections","params":[]}}"#;

const TEXT_EDIT_RPCS: &str =
    r#"{"method":"edit","params":{"view_id":"view-id-1","method":"insert","params":{"chars":"a"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"delete_backward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"delete_forward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"delete_word_forward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"delete_word_backward","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"delete_to_end_of_paragraph","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"insert_newline","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"insert_tab","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"yank","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"undo","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"redo","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"transpose","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"uppercase","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"lowercase","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"indent","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"outdent","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"duplicate_line","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"replace_next","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"replace_all","params":[]}}
{"id":2,"method":"edit","params":{"view_id":"view-id-1","method":"cut","params":[]}}"#;

const OTHER_EDIT_RPCS: &str = r#"{"method":"edit","params":{"view_id":"view-id-1","method":"scroll","params":[0,1]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"goto_line","params":{"line":1}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"request_lines","params":[0,1]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"drag","params":[17,15,0]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "toggle_sel"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "point_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "range_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "line_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "word_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "multi_line_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"gesture","params":{"line": 1, "col": 2, "ty": "multi_word_select"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"find","params":{"case_sensitive":false,"chars":"m"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"multi_find","params":{"queries": [{"case_sensitive":false,"chars":"m"}]}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"find_next","params":{"wrap_around":true}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"find_previous","params":{"wrap_around":true}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"find_all","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"highlight_find","params":{"visible":true}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"selection_for_find","params":{"case_sensitive":true}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"replace","params":{"chars":"a"}}}
{"method":"edit","params":{"view_id":"view-id-1","method":"selection_for_replace","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"debug_rewrap","params":[]}}
{"method":"edit","params":{"view_id":"view-id-1","method":"debug_print_spans","params":[]}}
{"id":3,"method":"edit","params":{"view_id":"view-id-1","method":"copy","params":[]}}"#;