From c944113242a5efc0b8e5ed68fa444b220abea592 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 22 May 2017 14:59:54 -0400
Subject: [PATCH 27/31] FIXME: implement toy client IDE in Python

---
 gcc/lsp-main.c                      |  19 ++++---
 gcc/lsp.h                           |   3 ++
 gcc/testsuite/gcc.dg/lsp/lsp.py     |   4 +-
 gcc/testsuite/gcc.dg/lsp/test.py    |   6 +--
 gcc/testsuite/gcc.dg/lsp/toy-ide.py | 105 ++++++++++++++++++++++++++++++++++++
 5 files changed, 127 insertions(+), 10 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/lsp/toy-ide.py

diff --git a/gcc/lsp-main.c b/gcc/lsp-main.c
index 00c7940..5c63a7e 100644
--- a/gcc/lsp-main.c
+++ b/gcc/lsp-main.c
@@ -65,10 +65,15 @@ Location_from_blt_node (const blt_node *node, Location &out)
     return false;
 
   out.uri = el_start.file;
-  out.range.start.line = el_start.line;
-  out.range.start.character = el_start.column;
-  out.range.end.line = el_finish.line;
-  out.range.end.character = el_finish.column;
+  /* Convert from GCC's 1-based lines and columns to LSP's
+     0-based lines and columns.
+     Offset the end column back by 1 since blt_node finish
+     locations are inclusive, whereas in LSP the endpoint of a Range
+     is exclusive.  */
+  out.range.start.line = el_start.line - 1;
+  out.range.start.character = el_start.column - 1;
+  out.range.end.line = el_finish.line - 1;
+  out.range.end.character = el_finish.column - 1 + 1;
   return true;
 }
 
@@ -96,10 +101,12 @@ gcc_lsp_server::do_text_document_definition (const TextDocumentPositionParams &p
 					     vec<Location> &out)
 {
   /* TODO.  */
+  /* Convert from LSP's 0-based lines and columns to GCC's
+     1-based lines and columns.  */
   blt_node *blt
     = the_blt_root_node->get_descendant_at_location (p.textDocument.uri,
-						     p.position.line,
-						     p.position.character);
+						     p.position.line + 1,
+						     p.position.character + 1);
   if (!blt)
     /* No results.  */
     return;
diff --git a/gcc/lsp.h b/gcc/lsp.h
index 88f305f..0812a69 100644
--- a/gcc/lsp.h
+++ b/gcc/lsp.h
@@ -26,6 +26,9 @@ typedef const char *DocumentUri;
 
 /* Interfaces from the protocol specification (which uses camel case).  */
 
+/* Note that LSP uses 0-based lines and characters, whereas GCC uses
+   1-based lines and columns.  */
+
 struct Position
 {
   static Position from_json (const json::value *params,
diff --git a/gcc/testsuite/gcc.dg/lsp/lsp.py b/gcc/testsuite/gcc.dg/lsp/lsp.py
index f86aca6..5027c84 100644
--- a/gcc/testsuite/gcc.dg/lsp/lsp.py
+++ b/gcc/testsuite/gcc.dg/lsp/lsp.py
@@ -58,8 +58,10 @@ class Location:
         print('%s:%i:%i: %s' % (self.uri, self.range.start.line,
                                 self.range.start.character, msg))
         # FIXME: underline
+        # linecache uses 1-based line numbers, whereas LSP uses
+        # 0-based line numbers
         import linecache
-        line = linecache.getline(self.uri, self.range.start.line)
+        line = linecache.getline(self.uri, self.range.start.line + 1)
         print('line: %r' % line)
 
 class TextDocumentIdentifier:
diff --git a/gcc/testsuite/gcc.dg/lsp/test.py b/gcc/testsuite/gcc.dg/lsp/test.py
index 52effc0..b380091 100644
--- a/gcc/testsuite/gcc.dg/lsp/test.py
+++ b/gcc/testsuite/gcc.dg/lsp/test.py
@@ -8,15 +8,15 @@ def main():
 
     url = "http://localhost:4000/jsonrpc"
     proxy = Proxy(url)
-    # Ask for the location of a usage of "struct foo"
+    # Ask for the location of a usage of "struct foo" (0-based lines)
     result = proxy.goto_definition(TextDocumentIdentifier('test.c'),
-                                   Position(9, 17))
+                                   Position(8, 16))
 
     # We expect to get back the location of where "struct foo" is defined
     print(result)
     # FIXME: filename/uri (currently a particular relative location)
     assert result == [Location(u'../../src/gcc/testsuite/gcc.dg/lsp/test.c',
-                               Range(Position(3, 1), Position(7, 1)))]
+                               Range(Position(2, 0), Position(6, 1)))]
     for loc in result:
         loc.dump("definition")
 
diff --git a/gcc/testsuite/gcc.dg/lsp/toy-ide.py b/gcc/testsuite/gcc.dg/lsp/toy-ide.py
new file mode 100644
index 0000000..00c47b3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lsp/toy-ide.py
@@ -0,0 +1,105 @@
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gtksourceview2
+#help(gtksourceview2)
+
+import lsp
+
+class ToyIde:
+    def delete_event(self, widget, event, data=None):
+        return False
+
+    def destroy(self, widget, data=None):
+        gtk.main_quit()
+
+    def quit_cb(self, b):
+        gtk.main_quit()
+
+    def get_cursor_position(self):
+        """Get the position of the cursor within the buffer
+        as an lsp.Position"""
+        mark = self.buf.get_insert()
+        print(mark)
+        iter = self.buf.get_iter_at_mark(mark)
+        print(iter)
+
+        print('line: %r' % iter.get_line()) # 0-based line
+        print('line_offset: %r' % iter.get_line_offset()) # 0-based offse
+
+        return lsp.Position(iter.get_line(), iter.get_line_offset())
+
+    def goto_definition_cb(self, b):
+        print "goto_definition_cb"
+        locs = self.lsp.goto_definition(lsp.TextDocumentIdentifier('test.c'),
+                                        self.get_cursor_position())
+        print(locs)
+        if len(locs) == 1:
+            loc = locs[0]
+            # Both lsp.Range and gtk.TextBuffer.select_range are inclusive
+            # on the start, exclusive on the end-point
+            self.buf.select_range(
+                self.buf.get_iter_at_line_offset(loc.range.start.line,
+                                                 loc.range.start.character),
+                self.buf.get_iter_at_line_offset(loc.range.end.line,
+                                                 loc.range.end.character))
+
+    def __init__(self, path):
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        self.window.connect("delete_event", self.delete_event)
+        self.window.connect("destroy", self.destroy)
+        self.window.set_size_request(640, 480)
+        vbox = gtk.VBox()
+        self.window.add(vbox)
+
+        uimanager = gtk.UIManager()
+        accelgroup = uimanager.get_accel_group()
+        self.window.add_accel_group(accelgroup)
+
+        actiongroup = gtk.ActionGroup('UIManagerExample')
+        self.actiongroup = actiongroup
+        actiongroup.add_actions([('Quit', gtk.STOCK_QUIT, '_Quit', None,
+                                  'Quit', self.quit_cb),
+                                 ('File', None, '_File'),
+                                 ('Test', None, '_Test'),
+                                 ('GotoDefinition', None, 'Goto _Definition',
+                                  None, 'Goto the definition of this thing',
+                                  self.goto_definition_cb)
+                             ])
+        actiongroup.get_action('Quit').set_property('short-label', '_Quit')
+        uimanager.insert_action_group(actiongroup, 0)
+
+        merge_id = uimanager.add_ui_from_string("""
+ <ui>
+    <menubar name="MenuBar">
+      <menu action="File">
+        <menuitem action="Quit"/>
+      </menu>
+      <menu action="Test">
+        <menuitem action="GotoDefinition"/>
+      </menu>
+    </menubar>
+  </ui>
+""")
+        menubar = uimanager.get_widget('/MenuBar')
+        vbox.pack_start(menubar, False)
+
+        self.buf = gtksourceview2.Buffer()
+
+        with open(path) as f:
+            text = f.read()
+        self.buf.set_text(text)
+
+        self.sv = gtksourceview2.View(buffer=self.buf)
+        self.sv.set_show_line_numbers(True)
+
+        vbox.add(self.sv)
+        self.window.show_all()
+
+        self.lsp = lsp.Proxy('http://localhost:4000/jsonrpc')
+
+    def main(self):
+        gtk.main()
+
+ide = ToyIde("gcc/testsuite/gcc.dg/lsp/test.c")
+ide.main()
-- 
1.8.5.3

