diff options
author | Tristan Gingold <tgingold@free.fr> | 2020-03-09 18:19:38 +0100 |
---|---|---|
committer | Tristan Gingold <tgingold@free.fr> | 2020-03-09 18:19:38 +0100 |
commit | 85627172aea75430ccd809ea0a13f3c4ed3ea8a0 (patch) | |
tree | 39a78d01afe31d9450ff316eaa9b923b91091af6 /python/vhdl_langserver/document.py | |
parent | 23935c8f2849fcb36bd69bbcadd4a0660912663f (diff) | |
download | ghdl-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.py | 208 |
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 |