diff --git a/doc/server.org b/doc/server.org index 6916ebe6b..4e97d5dda 100644 --- a/doc/server.org +++ b/doc/server.org @@ -13,13 +13,27 @@ to the standard input. ** Load file #+BEGIN_SRC -FILE [file-name] +LOAD [file-name] #+END_SRC This command loads the Lean file named =[file-name]=. Lean will create a "snapshot" (aka backtracking point) after each command. Lean uses the "snapshots" to process incremental updates efficiently. +** Visit file + +#+BEGIN_SRC +VISIT [file-name] +#+END_SRC + +Lean can keep information about multiple files. This command +sets =[file-name]= as the "current" file. The remaining commands +are all with respect to the current file. If =[file-name]= has not been +loaded yet, then this command will load it. Some of the remaining commands +apply "changes" to the current file. The =LOAD= command can be used to +discard all these changes, and enforce the content of the file stored +in file system. + ** Replace line #+BEGIN_SRC @@ -27,7 +41,7 @@ REPLACE [line-number] [new-line] #+END_SRC -This command replaces the line =[line-number]= with =[new-line]=. +This command replaces the line =[line-number]= (in the current file) with =[new-line]=. Lean uses the snapshots to process the request efficiently. If =[line-number]= is greater than the total number of lines in the lean buffer, then empty lines are introduced. The lines are indexed from 1. @@ -39,7 +53,7 @@ INSERT [line-number] [new-line] #+END_SRC -This command inserts =[new-line]= before line =[line-number]=. +This command inserts =[new-line]= (in the current file) before line =[line-number]=. If =[line-number]= is greater than the total number of lines in the lean buffer, then empty lines are introduced. The lines are indexed from 1. @@ -49,7 +63,7 @@ buffer, then empty lines are introduced. The lines are indexed from 1. REMOVE [line-number] #+END_SRC -Remove line =[line-number]=. The lines are indexed from 1. +Remove line =[line-number]= (in the current file). The lines are indexed from 1. If =[line-number]= is greater than the total number of lines in the lean buffer, then the command is ignored. @@ -59,7 +73,8 @@ buffer, then the command is ignored. INFO [line-number] #+END_SRC -This command extracts typing information associated with line =[line-number]=. +This command extracts typing information associated with line +=[line-number]= (in the current file). Lean produces a possible empty sequence of entries terminated with #+BEGIN_SRC @@ -108,3 +123,22 @@ num -- ACK -- ENDINFO #+END_SRC + +** Check line + +As described above, several commands can be used to apply +modifications to opened/visited files. These modification reflect +modifications performed by the text editor. The command =CHECK= can be +used to double check whether the text editor and Lean have the "same +view" of the current file + modifications. + +The following commands returns =-- OK= if the line =[line-number]= in +the current file is =[line]=. It returns =-- MISMATCH line out of +range=, if =[line-number]= is too big, and =-- MISMATCH expected +[lean-line]= when there is a mismatch, and Lean expects +=[line-number]= to be =[lean-line]=. + +#+BEGIN_SRC +-- CHECK [line-number] +[line] +#+END_SRC diff --git a/src/frontends/lean/server.cpp b/src/frontends/lean/server.cpp index 42c357920..0d68adf94 100644 --- a/src/frontends/lean/server.cpp +++ b/src/frontends/lean/server.cpp @@ -11,15 +11,77 @@ Author: Leonardo de Moura #include "frontends/lean/parser.h" namespace lean { -server::server(environment const & env, io_state const & ios, unsigned num_threads): - m_env(env), m_options(ios.get_options()), m_ios(ios), m_num_threads(num_threads), - m_empty_snapshot(m_env, m_options), m_from(0) { +server::file::file(std::string const & fname):m_fname(fname), m_from(0) {} + +void server::file::replace_line(unsigned linenum, std::string const & new_line) { + while (linenum >= m_lines.size()) + m_lines.push_back(""); + m_lines[linenum] = new_line; + if (linenum < m_from) + m_from = linenum; } -static std::string g_file("FILE"); +void server::file::insert_line(unsigned linenum, std::string const & new_line) { + while (linenum >= m_lines.size()) + m_lines.push_back(""); + m_lines.push_back(""); + lean_assert(m_lines.size() >= linenum+1); + unsigned i = m_lines.size(); + while (i > linenum) { + --i; + m_lines[i] = m_lines[i-1]; + } + m_lines[linenum] = new_line; + if (linenum < m_from) + m_from = linenum; +} + +void server::file::remove_line(unsigned linenum) { + if (linenum >= m_lines.size()) + return; + lean_assert(!m_lines.empty()); + for (unsigned i = linenum; i < m_lines.size()-1; i++) + m_lines[i] = m_lines[i+1]; + m_lines.pop_back(); + if (linenum < m_from) + m_from = linenum; +} + +/** + \brief Return index i <= m_snapshots.size() s.t. + * forall j < i, m_snapshots[j].m_line < line + * forall i <= j < m_snapshots.size(), m_snapshots[j].m_line >= line +*/ +unsigned server::file::find(unsigned linenum) { + unsigned low = 0; + unsigned high = m_snapshots.size(); + while (true) { + lean_assert(low <= high); + if (low == high) + return low; + unsigned mid = low + ((high - low)/2); + lean_assert(low <= mid && mid < high); + lean_assert(mid < m_snapshots.size()); + snapshot const & s = m_snapshots[mid]; + if (s.m_line < linenum) { + low = mid+1; + } else { + high = mid; + } + } +} + +server::server(environment const & env, io_state const & ios, unsigned num_threads): + m_env(env), m_options(ios.get_options()), m_ios(ios), m_out(ios.get_regular_channel().get_stream()), + m_num_threads(num_threads), m_empty_snapshot(m_env, m_options) { +} + +static std::string g_load("LOAD"); +static std::string g_visit("VISIT"); static std::string g_replace("REPLACE"); static std::string g_insert("INSERT"); static std::string g_remove("REMOVE"); +static std::string g_check("CHECK"); static std::string g_info("INFO"); static bool is_command(std::string const & cmd, std::string const & line) { @@ -40,112 +102,61 @@ static std::string & trim(std::string & s) { return ltrim(rtrim(s)); } -/** - \brief Return index i <= m_snapshots.size() s.t. - * forall j < i, m_snapshots[j].m_line < line - * forall i <= j < m_snapshots.size(), m_snapshots[j].m_line >= line -*/ -unsigned server::find(unsigned linenum) { - unsigned low = 0; - unsigned high = m_snapshots.size(); - while (true) { - lean_assert(low <= high); - if (low == high) - return low; - unsigned mid = low + ((high - low)/2); - lean_assert(low <= mid && mid < high); - lean_assert(mid < m_snapshots.size()); - snapshot const & s = m_snapshots[mid]; - if (s.m_line < linenum) { - low = mid+1; - } else { - high = mid; - } - } -} - void server::process_from(unsigned linenum) { - unsigned i = find(linenum); - m_snapshots.resize(i); - snapshot & s = i == 0 ? m_empty_snapshot : m_snapshots[i-1]; + unsigned i = m_file->find(linenum); + m_file->m_snapshots.resize(i); + snapshot & s = i == 0 ? m_empty_snapshot : m_file->m_snapshots[i-1]; std::string block; lean_assert(s.m_line > 0); - m_info.invalidate(s.m_line-1); - for (unsigned j = s.m_line-1; j < m_lines.size(); j++) { - block += m_lines[j]; + m_file->m_info.invalidate(s.m_line-1); + for (unsigned j = s.m_line-1; j < m_file->m_lines.size(); j++) { + block += m_file->m_lines[j]; block += '\n'; } std::istringstream strm(block); m_ios.set_options(s.m_options); - parser p(s.m_env, m_ios, strm, m_fname.c_str(), false, 1, s.m_lds, s.m_eds, s.m_line, - &m_snapshots, &m_info); + parser p(s.m_env, m_ios, strm, m_file->m_fname.c_str(), false, 1, s.m_lds, s.m_eds, s.m_line, + &m_file->m_snapshots, &m_file->m_info); p.set_cache(&m_cache); p(); } void server::update() { - if (m_from == m_lines.size()) + if (m_file->m_from == m_file->m_lines.size()) return; - process_from(m_from); - m_from = m_lines.size(); + process_from(m_file->m_from); + m_file->m_from = m_file->m_lines.size(); } -void server::read_file(std::string const & fname) { +void server::load_file(std::string const & fname) { std::ifstream in(fname); if (in.bad() || in.fail()) { - std::cout << "Error: failed to open file '" << fname << "'"; + m_out << "-- ERROR failed to open file '" << fname << "'" << std::endl; } else { - m_fname = fname; - m_lines.clear(); + m_file.reset(new file(fname)); + m_file_map.insert(mk_pair(fname, m_file)); for (std::string line; std::getline(in, line);) { - m_lines.push_back(line); + m_file->m_lines.push_back(line); } - m_from = 0; } } -void server::replace_line(unsigned linenum, std::string const & new_line) { - while (linenum >= m_lines.size()) - m_lines.push_back(""); - m_lines[linenum] = new_line; - if (linenum < m_from) - m_from = linenum; -} - -void server::insert_line(unsigned linenum, std::string const & new_line) { - while (linenum >= m_lines.size()) - m_lines.push_back(""); - m_lines.push_back(""); - lean_assert(m_lines.size() >= linenum+1); - unsigned i = m_lines.size(); - while (i > linenum) { - --i; - m_lines[i] = m_lines[i-1]; - } - m_lines[linenum] = new_line; - if (linenum < m_from) - m_from = linenum; -} - -void server::remove_line(unsigned linenum) { - if (linenum >= m_lines.size()) - return; - lean_assert(!m_lines.empty()); - for (unsigned i = linenum; i < m_lines.size()-1; i++) - m_lines[i] = m_lines[i+1]; - m_lines.pop_back(); - if (linenum < m_from) - m_from = linenum; +void server::visit_file(std::string const & fname) { + auto it = m_file_map.find(fname); + if (it == m_file_map.end()) + load_file(fname); + else + m_file = it->second; } void server::show_info(unsigned linenum) { update(); - unsigned i = find(linenum); - environment const & env = i == 0 ? m_env : m_snapshots[i-1].m_env; - options const & o = i == 0 ? m_options : m_snapshots[i-1].m_options; + unsigned i = m_file->find(linenum); + environment const & env = i == 0 ? m_env : m_file->m_snapshots[i-1].m_env; + options const & o = i == 0 ? m_options : m_file->m_snapshots[i-1].m_options; m_ios.set_options(o); io_state_stream out(env, m_ios); - m_info.display(out, linenum); + m_file->m_info.display(out, linenum); out << "-- ENDINFO" << endl; } @@ -164,23 +175,62 @@ unsigned server::get_linenum(std::string const & line, std::string const & cmd) return r; } +void server::check_file() { + if (!m_file) + throw exception("no file has been loaded/visited"); +} + +void server::replace_line(unsigned linenum, std::string const & new_line) { + check_file(); + m_file->replace_line(linenum, new_line); +} + +void server::insert_line(unsigned linenum, std::string const & new_line) { + check_file(); + m_file->insert_line(linenum, new_line); +} + +void server::remove_line(unsigned linenum) { + check_file(); + m_file->remove_line(linenum); +} + +void server::check_line(unsigned linenum, std::string const & line) { + check_file(); + if (linenum >= m_file->m_lines.size()) { + m_out << "-- MISMATCH line out of range" << std::endl; + } else if (m_file->m_lines[linenum] != line) { + m_out << "-- MISMATCH expected " << m_file->m_lines[linenum] << std::endl; + } else { + m_out << "-- OK" << std::endl; + } +} + bool server::operator()(std::istream & in) { for (std::string line; std::getline(in, line);) { try { - if (is_command(g_file, line)) { - std::string fname = line.substr(g_file.size()); + if (is_command(g_load, line)) { + std::string fname = line.substr(g_load.size()); trim(fname); - read_file(fname); + load_file(fname); + } else if (is_command(g_visit, line)) { + std::string fname = line.substr(g_visit.size()); + trim(fname); + visit_file(fname); + } else if (is_command(g_check, line)) { + unsigned linenum = get_linenum(line, g_check); + read_line(in, line); + check_line(linenum-1, line); } else if (is_command(g_replace, line)) { unsigned linenum = get_linenum(line, g_replace); read_line(in, line); replace_line(linenum-1, line); } else if (is_command(g_insert, line)) { - unsigned linenum = get_linenum(line, g_replace); + unsigned linenum = get_linenum(line, g_insert); read_line(in, line); insert_line(linenum-1, line); } else if (is_command(g_remove, line)) { - unsigned linenum = get_linenum(line, g_replace); + unsigned linenum = get_linenum(line, g_remove); remove_line(linenum-1); } else if (is_command(g_info, line)) { unsigned linenum = get_linenum(line, g_info); @@ -189,7 +239,7 @@ bool server::operator()(std::istream & in) { throw exception(sstream() << "unexpected command line: " << line); } } catch (exception & ex) { - std::cout << "Error: " << ex.what() << "\n"; + m_out << "-- ERROR " << ex.what() << std::endl; } } return true; diff --git a/src/frontends/lean/server.h b/src/frontends/lean/server.h index 7f8a6d0c7..e3343c36c 100644 --- a/src/frontends/lean/server.h +++ b/src/frontends/lean/server.h @@ -7,6 +7,7 @@ Author: Leonardo de Moura #pragma once #include #include +#include #include "library/definitions_cache.h" #include "frontends/lean/parser.h" #include "frontends/lean/info_manager.h" @@ -17,22 +18,38 @@ namespace lean { external processes. */ class server { - std::vector m_lines; - snapshot_vector m_snapshots; - info_manager m_info; + struct file { + std::string m_fname; + std::vector m_lines; + snapshot_vector m_snapshots; + info_manager m_info; + unsigned m_from; // mark the first modified line that was not processed yet. + + file(std::string const & fname); + unsigned find(unsigned linenum); + void replace_line(unsigned linenum, std::string const & new_line); + void insert_line(unsigned linenum, std::string const & new_line); + void remove_line(unsigned linenum); + }; + typedef std::shared_ptr file_ptr; + typedef std::unordered_map file_map; + file_map m_file_map; + file_ptr m_file; environment m_env; options m_options; io_state m_ios; + std::ostream & m_out; unsigned m_num_threads; snapshot m_empty_snapshot; - std::string m_fname; - unsigned m_from; definitions_cache m_cache; - void read_file(std::string const & fname); + void load_file(std::string const & fname); + void visit_file(std::string const & fname); + void check_file(); void replace_line(unsigned linenum, std::string const & new_line); void insert_line(unsigned linenum, std::string const & new_line); void remove_line(unsigned linenum); + void check_line(unsigned linenum, std::string const & line); void show_info(unsigned linenum); void process_from(unsigned linenum); unsigned find(unsigned linenum);