aboutsummaryrefslogtreecommitdiffstats
path: root/python/vhdl_langserver/document.py
blob: 26f02ba653b5b3f4de9a07cb18d420cdaa643efe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
generated by pre { line-height: 125%; margin: 0; }
td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight { background: #ffffff; }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
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