aboutsummaryrefslogtreecommitdiffstats
path: root/python/vhdl_langserver/document.py
diff options
context:
space:
mode:
authorTristan Gingold <tgingold@free.fr>2020-03-09 18:19:38 +0100
committerTristan Gingold <tgingold@free.fr>2020-03-09 18:19:38 +0100
commit85627172aea75430ccd809ea0a13f3c4ed3ea8a0 (patch)
tree39a78d01afe31d9450ff316eaa9b923b91091af6 /python/vhdl_langserver/document.py
parent23935c8f2849fcb36bd69bbcadd4a0660912663f (diff)
downloadghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.tar.gz
ghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.tar.bz2
ghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.zip
Import vhdl_langserver from ghdl-language-server
Diffstat (limited to 'python/vhdl_langserver/document.py')
-rw-r--r--python/vhdl_langserver/document.py208
1 files changed, 208 insertions, 0 deletions
diff --git a/python/vhdl_langserver/document.py b/python/vhdl_langserver/document.py
new file mode 100644
index 000000000..26f02ba65
--- /dev/null
+++ b/python/vhdl_langserver/document.py
@@ -0,0 +1,208 @@
+import ctypes
+import logging
+import os
+import libghdl.thin.name_table as name_table
+import libghdl.thin.files_map as files_map
+import libghdl.thin.files_map_editor as files_map_editor
+import libghdl.thin.libraries as libraries
+import libghdl.thin.vhdl.nodes as nodes
+import libghdl.thin.vhdl.sem_lib as sem_lib
+import libghdl.thin.vhdl.sem as sem
+import libghdl.thin.vhdl.formatters as formatters
+
+from . import symbols, references
+
+log = logging.getLogger(__name__)
+
+class Document(object):
+ # The encoding used for the files.
+ # Unfortunately this is not fully reliable. The client can read the
+ # file using its own view of the encoding. It then pass the document
+ # to the server using unicode(utf-8). Then the document is converted
+ # back to bytes using this encoding. And we hope the result would be
+ # the same as the file. Because VHDL uses the iso 8859-1 character
+ # set, we use the same encoding. The client should also use 8859-1.
+ encoding = 'iso-8859-1'
+
+ initial_gap_size = 4096
+
+ def __init__(self, uri, sfe=None, version=None):
+ self.uri = uri
+ self.version = version
+ self._fe = sfe
+ self.gap_size = Document.initial_gap_size
+ self._tree = nodes.Null_Iir
+
+ @staticmethod
+ def load(source, dirname, filename):
+ # Write text to file buffer.
+ src_bytes = source.encode(Document.encoding, "replace")
+ src_len = len(src_bytes)
+ buf_len = src_len + Document.initial_gap_size
+ fileid = name_table.Get_Identifier(filename.encode('utf-8'))
+ if os.path.isabs(filename):
+ dirid = name_table.Null_Identifier
+ else:
+ dirid = name_table.Get_Identifier(dirname.encode('utf-8'))
+ sfe = files_map.Reserve_Source_File(dirid, fileid, buf_len)
+ files_map_editor.Fill_Text(sfe, ctypes.c_char_p(src_bytes), src_len)
+ return sfe
+
+ def reload(self, source):
+ """Reload the source of a document. """
+ src_bytes = source.encode(Document.encoding, "replace")
+ files_map_editor.Fill_Text(self._fe,
+ ctypes.c_char_p(src_bytes), len(src_bytes))
+
+ def __str__(self):
+ return str(self.uri)
+
+ def apply_change(self, change):
+ """Apply a change to the document."""
+ text = change['text']
+ change_range = change.get('range')
+
+ text_bytes = text.encode(Document.encoding, "replace")
+
+ if not change_range:
+ # The whole file has changed
+ raise AssertionError
+ #if len(text_bytes) < thin.Files_Map.Get_Buffer_Length(self._fe):
+ # xxxx_replace
+ #else:
+ # xxxx_free
+ # xxxx_allocate
+ #return
+
+ start_line = change_range['start']['line']
+ start_col = change_range['start']['character']
+ end_line = change_range['end']['line']
+ end_col = change_range['end']['character']
+
+ status = files_map_editor.Replace_Text(
+ self._fe,
+ start_line + 1, start_col,
+ end_line + 1, end_col,
+ ctypes.c_char_p(text_bytes), len(text_bytes))
+ if status:
+ return
+
+ # Failed to replace text.
+ # Increase size
+ self.gap_size *= 2
+ fileid = files_map.Get_File_Name(self._fe)
+ dirid = files_map.Get_Directory_Name(self._fe)
+ buf_len = files_map.Get_File_Length(self._fe) + len(text_bytes) + self.gap_size
+ files_map.Discard_Source_File(self._fe)
+ new_sfe = files_map.Reserve_Source_File(dirid, fileid, buf_len)
+ files_map_editor.Copy_Source_File(new_sfe, self._fe)
+ files_map.Free_Source_File(self._fe)
+ self._fe = new_sfe
+ status = files_map_editor.Replace_Text(
+ self._fe,
+ start_line + 1, start_col,
+ end_line + 1, end_col,
+ ctypes.c_char_p(text_bytes), len(text_bytes))
+ assert status
+
+ def check_document(self, text):
+ log.debug("Checking document: %s", self.uri)
+
+ text_bytes = text.encode(Document.encoding, "replace")
+
+ files_map_editor.Check_Buffer_Content(
+ self._fe, ctypes.c_char_p(text_bytes), len(text_bytes))
+
+ @staticmethod
+ def add_to_library(tree):
+ # Detach the chain of units.
+ unit = nodes.Get_First_Design_Unit(tree)
+ nodes.Set_First_Design_Unit(tree, nodes.Null_Iir)
+ # FIXME: free the design file ?
+ tree = nodes.Null_Iir
+ # Analyze unit after unit.
+ while unit != nodes.Null_Iir:
+ # Pop the first unit.
+ next_unit = nodes.Get_Chain(unit)
+ nodes.Set_Chain(unit, nodes.Null_Iir)
+ lib_unit = nodes.Get_Library_Unit(unit)
+ if (lib_unit != nodes.Null_Iir
+ and nodes.Get_Identifier(unit) != name_table.Null_Identifier):
+ # Put the unit (only if it has a library unit) in the library.
+ libraries.Add_Design_Unit_Into_Library(unit, False)
+ tree = nodes.Get_Design_File(unit)
+ unit = next_unit
+ return tree
+
+ def parse_document(self):
+ """Parse a document and put the units in the library"""
+ assert self._tree == nodes.Null_Iir
+ tree = sem_lib.Load_File(self._fe)
+ if tree == nodes.Null_Iir:
+ return
+ self._tree = Document.add_to_library(tree)
+ log.debug("add_to_library(%u) -> %u", tree, self._tree)
+ if self._tree == nodes.Null_Iir:
+ return
+ nodes.Set_Design_File_Source(self._tree, self._fe)
+
+ def compute_diags(self):
+ log.debug("parse doc %d %s", self._fe, self.uri)
+ self.parse_document()
+ if self._tree == nodes.Null_Iir:
+ # No units, nothing to add.
+ return
+ # Semantic analysis.
+ unit = nodes.Get_First_Design_Unit(self._tree)
+ while unit != nodes.Null_Iir:
+ sem.Semantic(unit)
+ nodes.Set_Date_State(unit, nodes.Date_State.Analyze)
+ unit = nodes.Get_Chain(unit)
+
+ def flatten_symbols(self, syms, parent):
+ res = []
+ for s in syms:
+ s['location'] = {'uri': self.uri, 'range': s['range']}
+ del s['range']
+ s.pop('detail', None)
+ if parent is not None:
+ s['containerName'] = parent
+ res.append(s)
+ children = s.pop('children', None)
+ if children is not None:
+ res.extend(self.flatten_symbols(children, s))
+ return res
+
+ def document_symbols(self):
+ log.debug("document_symbols")
+ if self._tree == nodes.Null_Iir:
+ return []
+ syms = symbols.get_symbols_chain(self._fe, nodes.Get_First_Design_Unit(self._tree))
+ return self.flatten_symbols(syms, None)
+
+ def position_to_location(self, position):
+ pos = files_map.File_Line_To_Position(self._fe, position['line'] + 1)
+ return files_map.File_Pos_To_Location(self._fe, pos) + position['character']
+
+ def goto_definition(self, position):
+ loc = self.position_to_location(position)
+ return references.goto_definition(self._tree, loc)
+
+ def format_range(self, rng):
+ first_line = rng['start']['line'] + 1
+ last_line = rng['end']['line'] + (1 if rng['end']['character'] != 0 else 0)
+ if last_line < first_line:
+ return None
+ if self._tree == nodes.Null_Iir:
+ return None
+ hand = formatters.Allocate_Handle()
+ formatters.Indent_String(self._tree, hand, first_line, last_line)
+ buffer = formatters.Get_C_String(hand)
+ buf_len = formatters.Get_Length(hand)
+ newtext = buffer[:buf_len].decode(Document.encoding)
+ res = [ {'range': {
+ 'start': { 'line': first_line - 1, 'character': 0},
+ 'end': { 'line': last_line, 'character': 0}},
+ 'newText': newtext}]
+ formatters.Free_Handle(hand)
+ return res