├── .gitignore ├── lint.sh ├── version.vala.in ├── images ├── vls-gb.png ├── vls-vim.png └── vls-vscode.png ├── subprojects ├── libgee.wrap ├── json-glib.wrap ├── gobject-list-prince781.wrap └── jsonrpc-glib.wrap ├── .editorconfig ├── config.vala.in ├── test ├── meson.build └── testclient.vala ├── src ├── windows.vapi ├── analysis │ ├── codeanalyzer.vala │ ├── codelensanalyzer.vala │ ├── codestyleanalyzer.vala │ └── inlayhintnodes.vala ├── meson.build ├── request.vala ├── projects │ ├── buildtarget.vala │ ├── textdocument.vala │ ├── defaultproject.vala │ ├── ccproject.vala │ ├── filecache.vala │ ├── types.vala │ └── buildtask.vala ├── codeaction │ ├── baseconverteraction.vala │ ├── adddefaulttoswitchaction.vala │ ├── addotherconstantstoswitchaction.vala │ └── implementmissingprereqsaction.vala ├── reporter.vala ├── documentation │ ├── cnamemapper.vala │ └── doccomment.vala ├── codehelp │ ├── typehierarchy.vala │ ├── callhierarchy.vala │ ├── codeaction.vala │ ├── codelensengine.vala │ ├── find_scope.vala │ └── formatter.vala └── util.vala ├── meson_options.txt ├── .vala-lint.conf ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── FUNDING.yml └── workflows │ ├── vala-lts.yml │ └── vala-daily.yml ├── data ├── man │ └── vala-language-server.1.scd └── meson.build ├── meson.build ├── README.md └── vapi └── jsonrpc-glib-1.0.vapi /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.o 3 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | io.elementary.vala-lint -c .vala-lint.conf . 4 | -------------------------------------------------------------------------------- /version.vala.in: -------------------------------------------------------------------------------- 1 | namespace Config { 2 | const string PROJECT_VERSION = "@VCS_TAG@"; 3 | } 4 | -------------------------------------------------------------------------------- /images/vls-gb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vala-lang/vala-language-server/HEAD/images/vls-gb.png -------------------------------------------------------------------------------- /images/vls-vim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vala-lang/vala-language-server/HEAD/images/vls-vim.png -------------------------------------------------------------------------------- /images/vls-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vala-lang/vala-language-server/HEAD/images/vls-vscode.png -------------------------------------------------------------------------------- /subprojects/libgee.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://gitlab.gnome.org/GNOME/libgee 3 | revision = wip/tintou/meson 4 | 5 | [provide] 6 | gee-0.8 = gee_dep 7 | -------------------------------------------------------------------------------- /subprojects/json-glib.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://gitlab.gnome.org/GNOME/json-glib 3 | revision = 1.6.6 4 | 5 | [provide] 6 | json-glib-1.0 = json_glib_dep 7 | -------------------------------------------------------------------------------- /subprojects/gobject-list-prince781.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = gobject-list-prince781 3 | 4 | url = https://github.com/Prince781/gobject-list 5 | revision = head 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{vala,vapi}] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /subprojects/jsonrpc-glib.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://gitlab.gnome.org/GNOME/jsonrpc-glib 3 | revision = 3.42.0 4 | 5 | [provide] 6 | jsonrpc-glib-1.0 = libjsonrpc_glib_dep 7 | -------------------------------------------------------------------------------- /config.vala.in: -------------------------------------------------------------------------------- 1 | namespace Config { 2 | const string LIBVALA_VERSION = "@LIBVALA_VERSION@"; 3 | const string PROJECT_BUGSITE = "@PROJECT_BUGSITE@"; 4 | const string PROJECT_NAME = "@PROJECT_NAME@"; 5 | } 6 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | testclient_src = files([ 2 | 'testclient.vala', 3 | ]) 4 | 5 | executable('vls-testclient', 6 | dependencies: deps, 7 | sources: [testclient_src, extra_vala_sources], 8 | install: false) 9 | -------------------------------------------------------------------------------- /src/windows.vapi: -------------------------------------------------------------------------------- 1 | /** 2 | * A few native Win32 functions to fill some gaps. 3 | */ 4 | [CCode(cprefix = "", lower_case_cprefix = "", cheader_filename = "windows.h")] 5 | namespace Windows 6 | { 7 | void* _get_osfhandle(int fd); 8 | int _dup(int fd); 9 | int _dup2(int fd1, int fd2); 10 | int _close(int fd); 11 | } 12 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('active_parameter', type: 'boolean', value: false, description: 'Support activeParameter in signatureHelp') 2 | option('debug_mem', type: 'boolean', value: false, description: 'Debug memory usage') 3 | option('builder_abi', type: 'string', value: 'auto', description: 'Builder ABI version. Use a value like \'3.38\'') 4 | option('man_pages', type: 'feature', value: 'auto', description: 'Generate and install man pages.') 5 | option('tests', type: 'boolean', value: 'true', description: 'Build tests.') 6 | -------------------------------------------------------------------------------- /.vala-lint.conf: -------------------------------------------------------------------------------- 1 | [Checks] 2 | block-opening-brace-space-before=error 3 | double-semicolon=error 4 | double-spaces=error 5 | ellipsis=off 6 | line-length=warn 7 | naming-convention=off 8 | no-space=error 9 | note=warn 10 | space-before-paren=error 11 | use-of-tabs=error 12 | trailing-newlines=error 13 | trailing-whitespace=error 14 | unnecessary-string-template=error 15 | 16 | [Disabler] 17 | disable-by-inline-comments=true 18 | 19 | [line-length] 20 | max-line-length=120 21 | ignore-comments=true 22 | 23 | [naming-convention] 24 | exceptions=UUID, 25 | 26 | [note] 27 | keywords=TODO,FIXME, 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Software** 14 | OS and version (e.g. Ubuntu 20.04): 15 | Code editor (e.g. VSCode): 16 | Vala Language Server (e.g. git commit, or PPA/AUR version): 17 | Vala version (`valac --version`): 18 | 19 | **To Reproduce** 20 | Source code repo: 21 | 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Prince781 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/vala-lts.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master] 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: sudo add-apt-repository ppa:vala-team 15 | - run: sudo apt-get update 16 | - run: sudo apt-get install python3-setuptools valac libvala-0.56-dev libgee-0.8-dev libjsonrpc-glib-1.0-dev gobject-introspection libgirepository1.0-dev ninja-build 17 | - run: sudo pip3 install meson 18 | - run: meson build 19 | - run: ninja -C build 20 | - uses: elementary/actions/vala-lint@master 21 | with: 22 | conf: .vala-lint.conf 23 | fail: false 24 | -------------------------------------------------------------------------------- /.github/workflows/vala-daily.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master] 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: sudo add-apt-repository ppa:vala-team/daily 15 | - run: sudo apt-get update 16 | - run: sudo apt-get install python3-setuptools valac libvala-0.58-dev libgee-0.8-dev libjsonrpc-glib-1.0-dev gobject-introspection libgirepository1.0-dev ninja-build 17 | - run: sudo pip3 install meson 18 | - run: meson build 19 | - run: ninja -C build 20 | - uses: elementary/actions/vala-lint@master 21 | with: 22 | conf: .vala-lint.conf 23 | fail: false 24 | -------------------------------------------------------------------------------- /data/man/vala-language-server.1.scd: -------------------------------------------------------------------------------- 1 | vala-language-server(1) 2 | 3 | # NAME 4 | 5 | vala-language-server - Code Intelligence for Vala and Genie. 6 | 7 | # SYNOPSIS 8 | 9 | *vala-language-server* 10 | 11 | # DESCRIPTION 12 | 13 | vala-language-server is a language server protocol implementation for Vala 14 | and Genie. It enables features like auto-complete, go-to-definition, 15 | hover, symbol documentation, and more for editors/plugins supporting Vala 16 | and the Language Server Protocol. 17 | 18 | # ENVIRONMENT 19 | 20 | Users may set the *G_MESSAGES_DEBUG=all* environment variable when launching 21 | vala-language-server to enable debugging output. 22 | 23 | # BUGS 24 | 25 | Please file bugs at https://github.com/vala-lang/vala-language-server 26 | 27 | # AUTHOR 28 | 29 | Ben Iofel ++ 30 | Princeton Ferro 31 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man_pages')) 2 | if scdoc.found() 3 | scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) 4 | sh = find_program('sh', native: true) 5 | man_files = [ 6 | 'man/vala-language-server.1.scd', 7 | ] 8 | foreach filename : man_files 9 | topic = filename.split('.')[-3].split('/')[-1] 10 | if topic.contains('_') 11 | topic = topic.split('_')[0] + '.' + topic.split('_')[1] 12 | endif 13 | section = filename.split('.')[-2] 14 | output_filename = '@0@.@1@'.format(topic, section) 15 | 16 | man_file = configure_file( 17 | input: filename, 18 | output: output_filename, 19 | command: [sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output_filename)], 20 | ) 21 | 22 | install_man(man_file) 23 | endforeach 24 | endif 25 | -------------------------------------------------------------------------------- /src/analysis/codeanalyzer.vala: -------------------------------------------------------------------------------- 1 | /* codeanalyzer.vala 2 | * 3 | * Copyright 2021 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | interface Vls.CodeAnalyzer : Vala.CodeVisitor { 20 | public abstract DateTime last_updated { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | vls_src = files([ 2 | 'analysis/codeanalyzer.vala', 3 | 'analysis/codelensanalyzer.vala', 4 | 'analysis/codestyleanalyzer.vala', 5 | 'analysis/inlayhintnodes.vala', 6 | 'analysis/symbolenumerator.vala', 7 | 'codeaction/addotherconstantstoswitchaction.vala', 8 | 'codeaction/adddefaulttoswitchaction.vala', 9 | 'codeaction/baseconverteraction.vala', 10 | 'codeaction/implementmissingprereqsaction.vala', 11 | 'codehelp/callhierarchy.vala', 12 | 'codehelp/codeaction.vala', 13 | 'codehelp/codehelp.vala', 14 | 'codehelp/codelensengine.vala', 15 | 'codehelp/completionengine.vala', 16 | 'codehelp/find_scope.vala', 17 | 'codehelp/formatter.vala', 18 | 'codehelp/nodesearch.vala', 19 | 'codehelp/signaturehelpengine.vala', 20 | 'codehelp/symbolextractor.vala', 21 | 'codehelp/symbolreferences.vala', 22 | 'codehelp/symbolvisitor.vala', 23 | 'codehelp/typehierarchy.vala', 24 | 'documentation/cnamemapper.vala', 25 | 'documentation/doccomment.vala', 26 | 'documentation/girdocumentation.vala', 27 | 'projects/buildtarget.vala', 28 | 'projects/buildtask.vala', 29 | 'projects/ccproject.vala', 30 | 'projects/compilation.vala', 31 | 'projects/defaultproject.vala', 32 | 'projects/filecache.vala', 33 | 'projects/mesonproject.vala', 34 | 'projects/project.vala', 35 | 'projects/textdocument.vala', 36 | 'projects/types.vala', 37 | 'protocol.vala', 38 | 'reporter.vala', 39 | 'request.vala', 40 | 'server.vala', 41 | ]) 42 | 43 | if get_option('active_parameter') 44 | add_project_arguments(['--define=VALA_FEATURE_INITIAL_ARGUMENT_COUNT'], language: 'vala') 45 | endif 46 | 47 | vls = executable('vala-language-server', 48 | dependencies: deps, 49 | sources: [vls_src, conf_file, version_file, extra_vala_sources], 50 | c_args: ['-DG_LOG_DOMAIN="vls"'], 51 | install: true) 52 | -------------------------------------------------------------------------------- /src/request.vala: -------------------------------------------------------------------------------- 1 | /* servertypes.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * Represents a cancellable request from the client to the server. 21 | */ 22 | class Vls.Request : Object { 23 | private int64? int_value; 24 | private string? string_value; 25 | private string? method; 26 | 27 | public Request (Variant id, string? method = null) { 28 | assert (id.is_of_type (VariantType.INT64) || id.is_of_type (VariantType.STRING)); 29 | if (id.is_of_type (VariantType.INT64)) 30 | int_value = (int64) id; 31 | else 32 | string_value = (string) id; 33 | this.method = method; 34 | } 35 | 36 | public string to_string () { 37 | string id_string = int_value != null ? int_value.to_string () : string_value; 38 | return id_string + (method != null ? @":$method" : ""); 39 | } 40 | 41 | public static uint hash (Request req) { 42 | if (req.int_value != null) 43 | return GLib.int64_hash (req.int_value); 44 | else 45 | return GLib.str_hash (req.string_value); 46 | } 47 | 48 | public static bool equal (Request reqA, Request reqB) { 49 | if (reqA.int_value != null) { 50 | assert (reqB.int_value != null); 51 | return reqA.int_value == reqB.int_value; 52 | } else { 53 | assert (reqB.string_value != null); 54 | return reqA.string_value == reqB.string_value; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/projects/buildtarget.vala: -------------------------------------------------------------------------------- 1 | /* buildtarget.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | abstract class Vls.BuildTarget : Object, Hashable { 22 | public string output_dir { get; construct; } 23 | public string name { get; construct; } 24 | public string id { get; construct; } 25 | public int no { get; construct set; } 26 | 27 | /** 28 | * Input to the build target 29 | */ 30 | public ArrayList input { get; private set; default = new ArrayList (Util.file_equal); } 31 | 32 | /** 33 | * Output of the build target 34 | */ 35 | public ArrayList output { get; private set; default = new ArrayList (Util.file_equal); } 36 | 37 | public HashMap dependencies { get; private set; default = new HashMap (Util.file_hash, Util.file_equal); } 38 | 39 | /** 40 | * The time this target was last updated. Defaults to the start of the Unix epoch. 41 | */ 42 | public DateTime last_updated { get; protected set; default = new DateTime.from_unix_utc (0); } 43 | 44 | protected BuildTarget (string output_dir, string name, string id, int no) { 45 | Object (output_dir: output_dir, name: name, id: id, no: no); 46 | DirUtils.create_with_parents (output_dir, 0755); 47 | } 48 | 49 | /** 50 | * Build the target only if it needs to be built from its sources and if 51 | * its dependencies are newer than this target. This does not take care of 52 | * building the target's dependencies. 53 | */ 54 | public abstract void build_if_stale (Cancellable? cancellable = null) throws Error; 55 | 56 | public bool equal_to (BuildTarget other) { 57 | return output_dir == other.output_dir && name == other.name && id == other.id; 58 | } 59 | 60 | public uint hash () { 61 | return @"$output_dir::$name::$id".hash (); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/codeaction/baseconverteraction.vala: -------------------------------------------------------------------------------- 1 | /* baseconverteraction.vala 2 | * 3 | * Copyright 2022 JCWasmx86 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Lsp; 22 | using Gee; 23 | 24 | /** 25 | * The base converter code action allows to convert a constant to a different base. 26 | * For example: 27 | * ```vala 28 | * int x = 10; // actions: convert to 0xA or 012 29 | * ``` 30 | */ 31 | class Vls.BaseConverterAction : CodeAction { 32 | public BaseConverterAction (Vala.IntegerLiteral lit, VersionedTextDocumentIdentifier document) { 33 | string val = lit.value; 34 | // bool signed = lit.type_suffix[0] == 'u' || lit.type_suffix[0] == 'U'; 35 | bool negative = false; 36 | if (val[0] == '-') { 37 | negative = true; 38 | val = val.substring (1); 39 | } 40 | var workspace_edit = new WorkspaceEdit (); 41 | var document_edit = new TextDocumentEdit (document); 42 | var text_edit = new TextEdit (new Range.from_sourceref (lit.source_reference)); 43 | if (val.has_prefix ("0x")) { 44 | // base 16 -> base 8 45 | val = val.substring (2); 46 | text_edit.newText = "%s%#llo".printf (negative ? "-" : "", ulong.parse (val, 16)); 47 | this.title = "Convert hexadecimal value to octal"; 48 | } else if (val[0] == '0') { 49 | // base 8 -> base 10 50 | val = val.substring (1); 51 | text_edit.newText = "%s%#lld".printf (negative ? "-" : "", ulong.parse (val, 8)); 52 | this.title = "Convert octal value to decimal"; 53 | } else { 54 | // base 10 -> base 16 55 | text_edit.newText = "%s%#llx".printf (negative ? "-" : "", ulong.parse (val)); 56 | this.title = "Convert decimal value to hexadecimal"; 57 | } 58 | document_edit.edits.add (text_edit); 59 | workspace_edit.documentChanges = new ArrayList (); 60 | workspace_edit.documentChanges.add (document_edit); 61 | this.edit = workspace_edit; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('vala-language-server', 'vala', 'c', 2 | version: '0.48.7', 3 | license: 'LGPL2.1+', 4 | default_options: [ 5 | 'default_library=static', 6 | 'c_std=gnu11' # for C subprojects 7 | ] 8 | ) 9 | 10 | valac = meson.get_compiler('vala') 11 | libvala_version = run_command(valac, '--api-version').stdout().strip() 12 | if not libvala_version.version_compare('>=0.48' ) 13 | error('libvala needs to be 0.48 or above') 14 | endif 15 | 16 | extra_vala_sources = [] 17 | 18 | libvala = dependency('libvala-@0@'.format(libvala_version), version: '>= 0.48.12') 19 | 20 | libgobject_dep = dependency('gobject-2.0') 21 | libjsonrpc_glib_dep = dependency('jsonrpc-glib-1.0', version: '>= 3.28', required: false) 22 | if not libjsonrpc_glib_dep.found() 23 | # don't use introspection with static library as it requires a recent version of GIR 24 | jsonrpc_glib = subproject('jsonrpc-glib', default_options: ['with_introspection=false']) 25 | libjsonrpc_glib_dep = jsonrpc_glib.get_variable('libjsonrpc_glib_dep') 26 | extra_vala_sources += files('vapi/jsonrpc-glib-1.0.vapi') 27 | endif 28 | 29 | deps = [ 30 | dependency('glib-2.0'), 31 | libgobject_dep, 32 | dependency('gio-2.0'), 33 | dependency('gee-0.8'), 34 | dependency('json-glib-1.0'), 35 | libjsonrpc_glib_dep, 36 | libvala, 37 | valac.find_library('posix'), 38 | ] 39 | 40 | if libjsonrpc_glib_dep.version() >= '3.30' 41 | add_project_arguments(['--define=WITH_JSONRPC_GLIB_3_30'], language: 'vala') 42 | endif 43 | 44 | # let Vala add the appropriate defines for GLIB_X_X 45 | add_project_arguments(['--target-glib=auto'], language: 'vala') 46 | 47 | if get_option('debug_mem') 48 | libgobject_list_proj = subproject('gobject-list-prince781') 49 | deps += libgobject_list_proj.get_variable('libgobject_list_dep') 50 | endif 51 | 52 | conf = configuration_data() 53 | conf.set('LIBVALA_VERSION', libvala.version()) 54 | conf.set('PROJECT_BUGSITE', 'https://github.com/vala-lang/vala-language-server/issues') 55 | conf.set('PROJECT_NAME', meson.project_name()) 56 | 57 | conf_file = configure_file(input: 'config.vala.in', 58 | output: 'config.vala', 59 | configuration: conf) 60 | 61 | version_file = vcs_tag(input: 'version.vala.in', 62 | output: 'version.vala', 63 | command: ['git', 'describe', '--tags', '--dirty']) 64 | 65 | add_project_arguments(['--enable-gobject-tracing', '--fatal-warnings'], language: 'vala') 66 | 67 | extra_vala_sources += files([ 68 | 'src/util.vala' 69 | ]) 70 | 71 | if host_machine.system() == 'windows' 72 | deps += dependency('gio-windows-2.0') 73 | extra_vala_sources += files(['src/windows.vapi']) 74 | add_project_arguments(['--define=WINDOWS'], language: 'vala') 75 | else 76 | deps += dependency('gio-unix-2.0') 77 | endif 78 | 79 | subdir('data') 80 | subdir('src') 81 | 82 | if get_option('tests') 83 | subdir('test') 84 | endif 85 | -------------------------------------------------------------------------------- /src/projects/textdocument.vala: -------------------------------------------------------------------------------- 1 | /* textdocument.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Vala; 20 | 21 | class Vls.TextDocument : SourceFile { 22 | /** 23 | * This must be manually updated by anything that changes the content 24 | * of this document. 25 | */ 26 | public DateTime last_updated { get; set; default = new DateTime.now (); } 27 | public int version { get; set; } 28 | 29 | public int last_saved_version { get; private set; } 30 | private string? _last_saved_content = null; 31 | public string last_saved_content { 32 | get { 33 | if (_last_saved_content == null) 34 | return this.content; 35 | return _last_saved_content; 36 | } 37 | set { 38 | _last_saved_content = value; 39 | last_saved_version = version; 40 | } 41 | } 42 | 43 | private string? _last_fresh_content = null; 44 | 45 | /** 46 | * The contents at the time of compilation. 47 | */ 48 | public string last_fresh_content { 49 | get { 50 | if (_last_fresh_content == null) 51 | return this.content; 52 | return _last_fresh_content; 53 | } 54 | set { 55 | _last_fresh_content = value; 56 | } 57 | } 58 | 59 | public TextDocument (CodeContext context, File file, string? content = null, bool cmdline = false) throws FileError { 60 | string? cont = content; 61 | string uri = file.get_uri (); 62 | string? path = file.get_path (); 63 | path = path != null ? Util.realpath (path) : null; 64 | if (path != null && cont == null) 65 | FileUtils.get_contents (path, out cont); 66 | else if (path == null && cont == null) 67 | throw new FileError.NOENT (@"file $uri does not exist either on the system or in memory"); 68 | SourceFileType ftype; 69 | if (uri.has_suffix (".vapi") || uri.has_suffix (".gir")) 70 | ftype = SourceFileType.PACKAGE; 71 | else if (uri.has_suffix (".vala") || uri.has_suffix (".gs")) 72 | ftype = SourceFileType.SOURCE; 73 | else { 74 | ftype = SourceFileType.NONE; 75 | warning ("TextDocument: file %s is neither a package nor a source file", uri); 76 | } 77 | // prefer paths to URIs, unless we don't have a path 78 | // (this happens when we have just opened a new file in some editors) 79 | base (context, ftype, path ?? uri, cont, cmdline); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/codeaction/adddefaulttoswitchaction.vala: -------------------------------------------------------------------------------- 1 | /* adddefaulttoswitchaction.vala 2 | * 3 | * Copyright 2022 JCWasmx86 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-3.0-or-later 19 | */ 20 | 21 | using Lsp; 22 | using Gee; 23 | 24 | class Vls.AddDefaultToSwitchAction : CodeAction { 25 | public AddDefaultToSwitchAction (CodeActionContext context, 26 | Vala.SwitchStatement sws, 27 | VersionedTextDocumentIdentifier document, 28 | CodeStyleAnalyzer code_style) { 29 | this.title = "Add default case to switch-statement"; 30 | this.edit = new WorkspaceEdit (); 31 | 32 | var sections = sws.get_sections (); 33 | uint end_line, end_column; 34 | string label_indent, inner_indent; 35 | if (sections.is_empty) { 36 | end_line = sws.source_reference.end.line; 37 | end_column = sws.source_reference.end.column; 38 | Util.advance_past ((string)sws.source_reference.end.pos, /{/, ref end_line, ref end_column); 39 | label_indent = code_style.get_indentation (sws, 0); 40 | inner_indent = code_style.get_indentation (sws, 1); 41 | } else { 42 | var last_section = sections.last (); 43 | label_indent = code_style.get_indentation (last_section); 44 | Vala.SourceReference source_ref; 45 | if (last_section.get_statements ().is_empty) { 46 | source_ref = last_section.source_reference; 47 | inner_indent = code_style.get_indentation (last_section, 1); 48 | } else { 49 | var last_stmt = last_section.get_statements ().last (); 50 | source_ref = last_stmt.source_reference; 51 | inner_indent = code_style.get_indentation (last_stmt); 52 | } 53 | end_line = source_ref.end.line; 54 | end_column = source_ref.end.column; 55 | Util.advance_past ((string)source_ref.end.pos, /[;:]/, ref end_line, ref end_column); 56 | }; 57 | var insert_text = "%sdefault:\n%sassert_not_reached%*s();\n" 58 | .printf (label_indent, inner_indent, code_style.average_spacing_before_parens, ""); 59 | var document_edit = new TextDocumentEdit (document); 60 | var end_pos = new Position () { 61 | line = end_line - 1, 62 | character = end_column 63 | }; 64 | var text_edit = new TextEdit (new Range () { 65 | start = end_pos, 66 | end = end_pos 67 | }, insert_text); 68 | document_edit.edits.add (text_edit); 69 | this.edit.documentChanges = new ArrayList.wrap ({document_edit}); 70 | 71 | // now, include all relevant diagnostics 72 | foreach (var diag in context.diagnostics) 73 | if (diag.message.contains ("Switch does not handle")) 74 | add_diagnostic (diag); 75 | if (diagnostics != null && !diagnostics.is_empty) 76 | this.kind = "quickfix"; 77 | else 78 | this.kind = "refactor.rewrite"; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/reporter.vala: -------------------------------------------------------------------------------- 1 | /* reporter.vala 2 | * 3 | * Copyright 2017-2018 Ben Iofel 4 | * Copyright 2020 Princeton Ferro 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 2.1 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | using Lsp; 21 | using Gee; 22 | 23 | /* 24 | * Since we can have many of these, we want to keep this lightweight 25 | * by not extending GObject. 26 | */ 27 | class Vls.SourceMessage { 28 | public Vala.SourceReference? loc; 29 | public string message; 30 | public DiagnosticSeverity severity; 31 | 32 | public SourceMessage (Vala.SourceReference? loc, string message, DiagnosticSeverity severity) { 33 | this.loc = loc; 34 | this.message = message; 35 | this.severity = severity; 36 | } 37 | } 38 | 39 | class Vls.Reporter : Vala.Report { 40 | public bool fatal_warnings { get; private set; } 41 | public GenericArray messages = new GenericArray (); 42 | private HashMap> messages_by_srcref = new HashMap> (); 43 | 44 | public Reporter (bool fatal_warnings = false) { 45 | this.fatal_warnings = fatal_warnings; 46 | } 47 | 48 | public void add_message (Vala.SourceReference? source, string message, DiagnosticSeverity severity) { 49 | // mitigate potential infinite loop bugs in Vala parser 50 | HashMap? messages_count = null; 51 | if ((messages_count = messages_by_srcref[source.to_string ()]) == null) { 52 | messages_count = new HashMap (); 53 | messages_by_srcref[source.to_string ()] = messages_count; 54 | } 55 | if (!messages_count.has_key (message)) 56 | messages_count[message] = 0; 57 | messages_count[message] = messages_count[message] + 1; 58 | 59 | if (source != null && messages_count[message] >= 100) { 60 | GLib.error ("parser infinite loop detected! (seen \"%s\" @ %s at least %u times)\n" 61 | + "note: please report this bug with the source code that causes this error at https://gitlab.gnome.org/GNOME/vala", 62 | message, source.to_string (), messages_count[message]); 63 | } 64 | messages.add (new SourceMessage (source, message, severity)); 65 | } 66 | 67 | public override void depr (Vala.SourceReference? source, string message) { 68 | if (fatal_warnings) 69 | err (source, message); 70 | else { 71 | add_message (source, message, DiagnosticSeverity.Warning); 72 | ++warnings; 73 | } 74 | } 75 | public override void err (Vala.SourceReference? source, string message) { 76 | if (source == null) { // non-source compiler error 77 | stderr.printf ("Error: %s\n", message); 78 | } else { 79 | add_message (source, message, DiagnosticSeverity.Error); 80 | ++errors; 81 | } 82 | } 83 | public override void note (Vala.SourceReference? source, string message) { 84 | if (fatal_warnings) 85 | err (source, message); 86 | else { 87 | add_message (source, message, DiagnosticSeverity.Information); 88 | ++warnings; 89 | } 90 | } 91 | public override void warn (Vala.SourceReference? source, string message) { 92 | if (fatal_warnings) 93 | err (source, message); 94 | else { 95 | add_message (source, message, DiagnosticSeverity.Warning); 96 | ++warnings; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/codeaction/addotherconstantstoswitchaction.vala: -------------------------------------------------------------------------------- 1 | /* addotherconstantstoswitchaction.vala 2 | * 3 | * Copyright 2022 JCWasmx86 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-3.0-or-later 19 | */ 20 | 21 | using Gee; 22 | using Lsp; 23 | 24 | class Vls.AddOtherConstantsToSwitchAction : CodeAction { 25 | public AddOtherConstantsToSwitchAction (CodeActionContext context, 26 | Vala.SwitchStatement sws, 27 | VersionedTextDocumentIdentifier document, 28 | Vala.Enum e, 29 | HashSet missing, 30 | CodeStyleAnalyzer code_style) { 31 | this.title = "Add missing constants to switch"; 32 | this.edit = new WorkspaceEdit (); 33 | 34 | var sections = sws.get_sections (); 35 | uint end_line, end_column; 36 | string label_indent, inner_indent; 37 | if (sections.is_empty) { 38 | end_line = sws.source_reference.end.line; 39 | end_column = sws.source_reference.end.column; 40 | Util.advance_past ((string)sws.source_reference.end.pos, /{/, ref end_line, ref end_column); 41 | label_indent = code_style.get_indentation (sws); 42 | inner_indent = code_style.get_indentation (sws, 1); 43 | } else { 44 | var last_section = sections.last (); 45 | label_indent = code_style.get_indentation (last_section); 46 | Vala.SourceReference source_ref; 47 | if (last_section.get_statements ().is_empty) { 48 | source_ref = last_section.source_reference; 49 | inner_indent = code_style.get_indentation (last_section, 1); 50 | } else { 51 | var last_stmt = last_section.get_statements ().last (); 52 | source_ref = last_stmt.source_reference; 53 | inner_indent = code_style.get_indentation (last_stmt); 54 | } 55 | end_line = source_ref.end.line; 56 | end_column = source_ref.end.column; 57 | Util.advance_past ((string)source_ref.end.pos, /[;:]/, ref end_line, ref end_column); 58 | }; 59 | var sb = new StringBuilder (); 60 | foreach (var ev in e.get_values ()) { 61 | if (ev.name in missing) { 62 | sb.append (label_indent) 63 | .append ("case ") 64 | .append (ev.to_string ()) 65 | .append (":\n") 66 | .append (inner_indent) 67 | .append_printf ("assert_not_reached%*s();\n", code_style.average_spacing_before_parens, ""); 68 | } 69 | } 70 | var insert_text = sb.str; 71 | var document_edit = new TextDocumentEdit (document); 72 | var end_pos = new Position () { 73 | line = end_line - 1, 74 | character = end_column 75 | }; 76 | var text_edit = new TextEdit (new Range () { 77 | start = end_pos, 78 | end = end_pos 79 | }, insert_text); 80 | document_edit.edits.add (text_edit); 81 | this.edit.documentChanges = new ArrayList.wrap ({document_edit}); 82 | // now, include all relevant diagnostics 83 | foreach (var diag in context.diagnostics) 84 | if (diag.message.contains ("Switch does not handle")) 85 | add_diagnostic (diag); 86 | if (diagnostics != null && !diagnostics.is_empty) 87 | this.kind = "quickfix"; 88 | else 89 | this.kind = "refactor.rewrite"; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/documentation/cnamemapper.vala: -------------------------------------------------------------------------------- 1 | /* cnamemapper.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * Visits the symbols in a file and maps their C name. This is used by the 21 | * documentation engine to equalize references to symbols in VAPIs with 22 | * references to symbols in corresponding GIRs. It is also used to map 23 | * references to C names in documentation text to references to symbols 24 | * in VAPIs. 25 | * 26 | * @see Vls.CodeHelp.get_symbol_cname 27 | */ 28 | class Vls.CNameMapper : Vala.CodeVisitor { 29 | private Gee.HashMap cname_to_sym; 30 | 31 | public CNameMapper (Gee.HashMap cname_to_sym) { 32 | this.cname_to_sym = cname_to_sym; 33 | } 34 | 35 | private void map_cname (Vala.Symbol sym) { 36 | string cname = CodeHelp.get_symbol_cname (sym); 37 | // debug ("mapping C name %s -> symbol %s (%s)", cname, sym.get_full_name (), sym.type_name); 38 | if (!cname_to_sym.has_key (cname)) { 39 | cname_to_sym[cname] = sym; 40 | if (sym is Vala.ErrorDomain || sym is Vala.Enum) { 41 | // also map its C prefix (without the trailing underscore) 42 | string? cprefix = sym.get_attribute_string ("CCode", "cprefix"); 43 | MatchInfo match_info = null; 44 | if (cprefix != null && /^([A-Z]+(_[A-Z]+)*)_$/.match (cprefix, 0, out match_info)) { 45 | cname_to_sym[match_info.fetch (1)] = sym; 46 | } 47 | } 48 | } 49 | } 50 | 51 | public override void visit_source_file (Vala.SourceFile source_file) { 52 | source_file.accept_children (this); 53 | } 54 | 55 | public override void visit_class (Vala.Class cl) { 56 | map_cname (cl); 57 | cl.accept_children (this); 58 | } 59 | 60 | public override void visit_constant (Vala.Constant c) { 61 | map_cname (c); 62 | } 63 | 64 | public override void visit_creation_method (Vala.CreationMethod m) { 65 | map_cname (m); 66 | } 67 | 68 | public override void visit_delegate (Vala.Delegate d) { 69 | map_cname (d); 70 | } 71 | 72 | public override void visit_enum (Vala.Enum en) { 73 | map_cname (en); 74 | en.accept_children (this); 75 | } 76 | 77 | public override void visit_enum_value (Vala.EnumValue ev) { 78 | map_cname (ev); 79 | } 80 | 81 | public override void visit_error_domain (Vala.ErrorDomain edomain) { 82 | map_cname (edomain); 83 | edomain.accept_children (this); 84 | } 85 | 86 | public override void visit_error_code (Vala.ErrorCode ecode) { 87 | map_cname (ecode); 88 | } 89 | 90 | public override void visit_field (Vala.Field f) { 91 | map_cname (f); 92 | } 93 | 94 | public override void visit_interface (Vala.Interface iface) { 95 | map_cname (iface); 96 | iface.accept_children (this); 97 | } 98 | 99 | public override void visit_method (Vala.Method m) { 100 | map_cname (m); 101 | } 102 | 103 | public override void visit_namespace (Vala.Namespace ns) { 104 | map_cname (ns); 105 | ns.accept_children (this); 106 | } 107 | 108 | public override void visit_property (Vala.Property prop) { 109 | map_cname (prop); 110 | } 111 | 112 | public override void visit_signal (Vala.Signal sig) { 113 | map_cname (sig); 114 | } 115 | 116 | public override void visit_struct (Vala.Struct st) { 117 | map_cname (st); 118 | st.accept_children (this); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/codehelp/typehierarchy.vala: -------------------------------------------------------------------------------- 1 | /* typehierarchy.vala 2 | * 3 | * Copyright 2022 Princeton Ferro 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Vala; 22 | using Lsp; 23 | 24 | namespace Vls.TypeHierarchy { 25 | TypeHierarchyItem[] get_subtypes (Project project, TypeSymbol symbol) { 26 | TypeHierarchyItem[] subtypes = {}; 27 | 28 | var generated_vapis = new Gee.HashSet (Util.file_hash, Util.file_equal); 29 | foreach (var btarget in project.get_compilations ()) 30 | generated_vapis.add_all (btarget.output); 31 | var shown_files = new Gee.HashSet (Util.file_hash, Util.file_equal); 32 | foreach (var pair in SymbolReferences.get_compilations_using_symbol (project, symbol)) { 33 | foreach (var source_file in pair.first.code_context.get_source_files ()) { 34 | var gfile = File.new_for_commandline_arg (source_file.filename); 35 | // don't show symbol from generated VAPI 36 | if (gfile in generated_vapis || gfile in shown_files) 37 | continue; 38 | 39 | var compilation_type_symbol = SymbolReferences.find_matching_symbol (pair.first.code_context, symbol); 40 | Vala.CodeContext.push (pair.first.code_context); 41 | var result = new NodeSearch.with_filter ( 42 | source_file, 43 | compilation_type_symbol, 44 | (needle, node) => { 45 | if (needle is ObjectTypeSymbol && node is ObjectTypeSymbol) 46 | return node != needle && ((ObjectTypeSymbol)node).is_subtype_of ((ObjectTypeSymbol)needle); 47 | if (needle is Struct && node is Struct) 48 | return ((Struct)node).base_struct == (Struct)needle; 49 | return false; 50 | }, 51 | true 52 | ).result; 53 | foreach (var node in result) 54 | subtypes += new TypeHierarchyItem.from_symbol ((Vala.TypeSymbol)node); 55 | Vala.CodeContext.pop (); 56 | 57 | shown_files.add (gfile); 58 | } 59 | } 60 | 61 | return subtypes; 62 | } 63 | 64 | TypeHierarchyItem[] get_supertypes (Project project, TypeSymbol symbol) { 65 | TypeHierarchyItem[] supertypes = {}; 66 | 67 | if (symbol is ObjectTypeSymbol) { 68 | var ots = (ObjectTypeSymbol)symbol; 69 | foreach (var iface in ots.get_interfaces ()) { 70 | var real_iface = SymbolReferences.find_real_symbol (project, iface) as TypeSymbol; 71 | if (real_iface != null) 72 | supertypes += new TypeHierarchyItem.from_symbol (real_iface); 73 | } 74 | } 75 | if (symbol is Class) { 76 | var cls = (Class)symbol; 77 | foreach (var base_type in cls.get_base_types ()) { 78 | if (base_type.type_symbol != null) { 79 | var real_type_symbol = SymbolReferences.find_real_symbol (project, base_type.type_symbol) as TypeSymbol; 80 | if (real_type_symbol != null) 81 | supertypes += new TypeHierarchyItem.from_symbol (real_type_symbol); 82 | } 83 | } 84 | } else if (symbol is Struct) { 85 | var st = (Struct)symbol; 86 | if (st.base_type != null && st.base_type.type_symbol != null) { 87 | var real_type_symbol = SymbolReferences.find_real_symbol (project, st.base_type.type_symbol) as TypeSymbol; 88 | if (real_type_symbol != null) 89 | supertypes += new TypeHierarchyItem.from_symbol (real_type_symbol); 90 | } 91 | } 92 | 93 | return supertypes; 94 | } 95 | } -------------------------------------------------------------------------------- /src/analysis/codelensanalyzer.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | /** 4 | * Collects only those symbols of interest to the code lens. Currently these are: 5 | * 6 | * * methods and properties that override or implement a base symbol 7 | * * abstract and virtual methods and properties that are overridden 8 | * * methods and properties that hide a base symbol 9 | */ 10 | class Vls.CodeLensAnalyzer : Vala.CodeVisitor, CodeAnalyzer { 11 | public DateTime last_updated { get; set; } 12 | 13 | /** 14 | * Collection of methods/properties that override a base symbol. 15 | * 16 | * Maps a symbol to the base symbol it overrides. 17 | */ 18 | public HashMap found_overrides { get; private set; } 19 | 20 | /** 21 | * Collection of methods/properties that implement a base symbol. 22 | * 23 | * Maps a symbol to the abstract symbol it implements. 24 | */ 25 | public HashMap found_implementations { get; private set; } 26 | 27 | /** 28 | * Collection of methods/properties that hide a base symbol. 29 | * 30 | * Maps a symbol to the symbol it hides. 31 | */ 32 | public HashMap found_hides { get; private set; } 33 | 34 | private Vala.SourceFile file; 35 | 36 | public CodeLensAnalyzer (Vala.SourceFile file) { 37 | this.file = file; 38 | this.found_overrides = new HashMap (); 39 | this.found_implementations = new HashMap (); 40 | this.found_hides = new HashMap (); 41 | visit_source_file (file); 42 | } 43 | 44 | public override void visit_source_file (Vala.SourceFile file) { 45 | file.accept_children (this); 46 | } 47 | 48 | public override void visit_namespace (Vala.Namespace ns) { 49 | ns.accept_children (this); 50 | } 51 | 52 | public override void visit_class (Vala.Class cl) { 53 | if (cl.source_reference.file != null && cl.source_reference.file != file) 54 | return; 55 | cl.accept_children (this); 56 | } 57 | 58 | public override void visit_interface (Vala.Interface iface) { 59 | if (iface.source_reference.file != null && iface.source_reference.file != file) 60 | return; 61 | iface.accept_children (this); 62 | } 63 | 64 | public override void visit_struct (Vala.Struct st) { 65 | if (st.source_reference.file != null && st.source_reference.file != file) 66 | return; 67 | st.accept_children (this); 68 | } 69 | 70 | public override void visit_method (Vala.Method m) { 71 | if (m.source_reference.file != file) 72 | return; 73 | 74 | if (m.base_interface_method != null && m.base_interface_method != m) { 75 | if (CodeHelp.base_method_requires_override (m.base_interface_method)) 76 | found_overrides[m] = m.base_interface_method; 77 | else 78 | found_implementations[m] = m.base_interface_method; 79 | } else if (m.base_method != null && m.base_method != m) { 80 | if (CodeHelp.base_method_requires_override (m.base_method)) 81 | found_overrides[m] = m.base_method; 82 | else 83 | found_implementations[m] = m.base_method; 84 | } 85 | 86 | var hidden_member = m.get_hidden_member (); 87 | if (m.hides && hidden_member != null) 88 | found_hides[m] = hidden_member; 89 | } 90 | 91 | public override void visit_property (Vala.Property prop) { 92 | if (prop.source_reference.file != file) 93 | return; 94 | 95 | if (prop.base_interface_property != null && prop.base_interface_property != prop) { 96 | if (CodeHelp.base_property_requires_override (prop.base_interface_property)) 97 | found_overrides[prop] = prop.base_interface_property; 98 | else 99 | found_implementations[prop] = prop.base_interface_property; 100 | } else if (prop.base_property != null && prop.base_property != prop) { 101 | if (CodeHelp.base_property_requires_override (prop.base_property)) 102 | found_overrides[prop] = prop.base_property; 103 | else 104 | found_implementations[prop] = prop.base_property; 105 | } 106 | 107 | var hidden_member = prop.get_hidden_member (); 108 | if (prop.hides && hidden_member != null) 109 | found_hides[prop] = hidden_member; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/projects/defaultproject.vala: -------------------------------------------------------------------------------- 1 | /* defaultproject.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | /** 22 | * A project without any backend. Mainly useful for editing one file. 23 | */ 24 | class Vls.DefaultProject : Project { 25 | // we have these functions to prevent a ref to this 26 | 27 | static uint str_hash (string s) { 28 | return GLib.str_hash (s); 29 | } 30 | 31 | static bool str_equal (string s1, string s2) { 32 | return GLib.str_equal (s1, s2); 33 | } 34 | 35 | /** 36 | * List of opened files (path names) for each compilation. Single-file 37 | * compilations actually include multiple VAPI files, so we need to handle 38 | * cases where the user tries to open/close a VAPI that belongs to another 39 | * compilation. 40 | */ 41 | private HashMultiMap opened = new HashMultiMap ( 42 | null, 43 | null, 44 | str_hash, 45 | str_equal 46 | ); 47 | 48 | public DefaultProject (string root_path, FileCache file_cache) { 49 | base (root_path, file_cache); 50 | } 51 | 52 | public override bool reconfigure_if_stale (Cancellable? cancellable = null) throws Error { 53 | // this should do nothing, since we don't have a backend 54 | return false; 55 | } 56 | 57 | public override ArrayList> open (string escaped_uri, string? content = null, Cancellable? cancellable = null) throws Error { 58 | // create a new compilation 59 | var file = File.new_for_uri (Uri.unescape_string (escaped_uri)); 60 | string uri = file.get_uri (); 61 | string[] args = {}; 62 | 63 | var results = lookup_compile_input_source_file (escaped_uri); 64 | // if the file is already open (ex: glib.vapi) 65 | if (!results.is_empty) { 66 | foreach (var item in results) { 67 | // we may be opening a VAPI that is already a part of another 68 | // compilation, so ensure this file is marked as open 69 | opened[item.second] = item.first.filename; 70 | debug ("returning %s for %s", item.first.filename, uri); 71 | } 72 | return results; 73 | } 74 | 75 | // analyze interpeter line 76 | if (content != null && (content.has_prefix ("#!") || content.has_prefix ("//"))) { 77 | try { 78 | args = Util.get_arguments_from_command_str (content.substring (2, content.index_of_char ('\n'))); 79 | debug ("parsed %d argument(s) from interpreter line ...", args.length); 80 | for (int i = 0; i < args.length; i++) 81 | debug ("[arg %d] %s", i, args[i]); 82 | } catch (RegexError rerror) { 83 | warning ("failed to parse interpreter line"); 84 | } 85 | } 86 | var btarget = new Compilation (file_cache, root_path, uri, uri, build_targets.size, 87 | {"valac"}, args, {uri}, {}, {}, 88 | content != null ? new string[]{content} : null); 89 | // build it now so that information is available immediately on 90 | // file open (other projects compile on LSP initialize(), so they don't 91 | // need to do this) 92 | btarget.build_if_stale (cancellable); 93 | // make sure this comes after, that way btarget only gets added 94 | // if the build succeeds 95 | build_targets.add (btarget); 96 | debug ("added %s", uri); 97 | 98 | results = lookup_compile_input_source_file (escaped_uri); 99 | // mark only the requested filename as opened in this compilation 100 | foreach (var item in results) 101 | if (item.first.filename == file.get_path ()) 102 | opened[item.second] = item.first.filename; 103 | return results; 104 | } 105 | 106 | public override bool close (string escaped_uri) { 107 | bool targets_removed = false; 108 | foreach (var result in lookup_compile_input_source_file (escaped_uri)) { 109 | // we need to remove this target only if all of its open files have been closed 110 | if (opened.remove (result.second, result.first.filename) && !(result.second in opened)) { 111 | build_targets.remove (result.second); 112 | targets_removed = true; 113 | } 114 | } 115 | return targets_removed; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/projects/ccproject.vala: -------------------------------------------------------------------------------- 1 | /* ccproject.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | /** 22 | * A backend for `compile_commands.json` files. 23 | */ 24 | class Vls.CcProject : Project { 25 | private bool build_files_have_changed = true; 26 | private File cc_json_file; 27 | private HashMap build_files = new HashMap (Util.file_hash, Util.file_equal); 28 | 29 | private string build_dir; 30 | 31 | public override bool reconfigure_if_stale (Cancellable? cancellable = null) throws Error { 32 | if (!build_files_have_changed) { 33 | return false; 34 | } 35 | 36 | build_targets.clear (); 37 | build_files_have_changed = false; 38 | 39 | debug ("CcProject: configuring in build dir %s ...", build_dir); 40 | 41 | if (!build_files.has_key (cc_json_file)) { 42 | debug ("CcProject: obtaining a new file monitor for %s ...", cc_json_file.get_path ()); 43 | FileMonitor file_monitor = cc_json_file.monitor_file (FileMonitorFlags.NONE, cancellable); 44 | file_monitor.changed.connect (file_changed_event); 45 | build_files[cc_json_file] = file_monitor; 46 | } 47 | 48 | var parser = new Json.Parser.immutable_new (); 49 | parser.load_from_stream (cc_json_file.read (cancellable), cancellable); 50 | Json.Node? cc_json_root = parser.get_root (); 51 | 52 | if (cc_json_root == null) 53 | throw new ProjectError.INTROSPECTION (@"JSON root is null. Bailing out!"); 54 | 55 | // iterate over all compile commands 56 | int i = -1; 57 | foreach (Json.Node cc_node in cc_json_root.get_array ().get_elements ()) { 58 | i++; 59 | if (cc_node.get_node_type () != Json.NodeType.OBJECT) 60 | throw new ProjectError.INTROSPECTION (@"JSON node is not an object. Bailing out!"); 61 | var cc = Json.gobject_deserialize (typeof (CompileCommand), cc_node) as CompileCommand?; 62 | if (cc == null) 63 | throw new ProjectError.INTROSPECTION (@"JSON node is null. Bailing out!"); 64 | 65 | if (cc.command.length == 0) { 66 | warning ("CC#%d has empty command list", i); 67 | continue; 68 | } 69 | 70 | if (cc.command[0].contains ("valac")) 71 | build_targets.add (new Compilation (file_cache, cc.directory, cc.file ?? @"CC#$i", @"CC#$i", i, 72 | cc.command[0:1], cc.command[1:cc.command.length], 73 | new string[]{}, new string[]{}, new string[]{})); 74 | else 75 | build_targets.add (new BuildTask (file_cache, cc.directory, cc.directory, cc.file ?? @"CC#$i", @"CC#$i", i, 76 | cc.command[0:1], cc.command[1:cc.command.length], 77 | new string[]{}, new string[]{}, 78 | new string[]{}, "unknown")); 79 | } 80 | 81 | analyze_build_targets (cancellable); 82 | 83 | return true; 84 | } 85 | 86 | public CcProject (string root_path, string cc_location, FileCache file_cache, Cancellable? cancellable = null) throws Error { 87 | base (root_path, file_cache); 88 | 89 | var root_dir = File.new_for_path (root_path); 90 | var cc_json_file = File.new_for_commandline_arg_and_cwd (cc_location, root_path); 91 | string? relative_path = root_dir.get_relative_path (cc_json_file); 92 | 93 | if (relative_path == null) { 94 | throw new ProjectError.INTROSPECTION (@"$cc_location is not relative to project root"); 95 | } 96 | 97 | this.build_dir = cc_json_file.get_parent ().get_path (); 98 | this.cc_json_file = cc_json_file; 99 | 100 | reconfigure_if_stale (cancellable); 101 | } 102 | 103 | private void file_changed_event (File src, File? dest, FileMonitorEvent event_type) { 104 | if (FileMonitorEvent.ATTRIBUTE_CHANGED in event_type) { 105 | debug ("CcProject: watched file %s had an attribute changed", src.get_path ()); 106 | build_files_have_changed = true; 107 | changed (); 108 | } 109 | if (FileMonitorEvent.CHANGED in event_type) { 110 | debug ("CcProject: watched file %s was changed", src.get_path ()); 111 | build_files_have_changed = true; 112 | changed (); 113 | } 114 | if (FileMonitorEvent.DELETED in event_type) { 115 | debug ("CcProject: watched file %s was deleted", src.get_path ()); 116 | // remove this file monitor since the file was deleted 117 | FileMonitor file_monitor; 118 | if (build_files.unset (src, out file_monitor)) { 119 | file_monitor.cancel (); 120 | file_monitor.changed.disconnect (file_changed_event); 121 | } 122 | build_files_have_changed = true; 123 | changed (); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/projects/filecache.vala: -------------------------------------------------------------------------------- 1 | /* filecache.vala 2 | * 3 | * Copyright 2022 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | /** 22 | * In-memory file metadata cache. Used to check whether files remain the same 23 | * after context updates. 24 | */ 25 | class Vls.FileCache : Object { 26 | /** 27 | * File metadata. 28 | */ 29 | public class ContentStatus { 30 | /** 31 | * The time this information was last updated. This may be earlier than 32 | * the time the file was last updated if the file is the same after an 33 | * update. 34 | */ 35 | public DateTime last_updated { get; set; } 36 | 37 | /** 38 | * The time this file was last updated. Can be null if we're unable to 39 | * query this info from the file system. 40 | */ 41 | public DateTime? file_last_updated { get; set; } 42 | 43 | /** 44 | * The size of the file. 45 | */ 46 | public size_t size { get; set; } 47 | 48 | /** 49 | * The checksum of the file. 50 | */ 51 | public string checksum { get; set; } 52 | 53 | /** 54 | * Create a new content status. 55 | * 56 | * @param data data loaded from a file 57 | * @param last_modified the last time the file was modified 58 | */ 59 | public ContentStatus (Bytes data, DateTime? last_modified) { 60 | this.last_updated = new DateTime.now (); 61 | this.file_last_updated = last_modified; 62 | this.size = data.get_size (); 63 | // MD5 is fastest and we don't have any security issues even if there are collisions 64 | this.checksum = Checksum.compute_for_bytes (ChecksumType.MD5, data); 65 | } 66 | 67 | /** 68 | * Create a new content status for an empty/non-existent file. 69 | */ 70 | public ContentStatus.empty () { 71 | this.last_updated = new DateTime.now (); 72 | this.file_last_updated = null; 73 | this.size = 0; 74 | this.checksum = Checksum.compute_for_data (ChecksumType.MD5, {}); 75 | } 76 | } 77 | 78 | private HashMap _content_cache; 79 | 80 | public FileCache () { 81 | _content_cache = new HashMap (Util.file_hash, Util.file_equal); 82 | } 83 | 84 | /** 85 | * Updates the file in the cache. Will perform I/O, but will check the file 86 | * modification time first. If the file does not exist, its metadata will 87 | * be created in an empty configuration. 88 | * 89 | * @param file the file to add or update in the cache 90 | * @param cancellable (optional) a way to cancel the I/O operation 91 | */ 92 | public void update (File file, Cancellable? cancellable = null) throws Error { 93 | ContentStatus? status = _content_cache[file]; 94 | DateTime? last_modified = null; 95 | bool file_exists = false; 96 | try { 97 | FileInfo info = file.query_info (FileAttribute.TIME_MODIFIED, FileQueryInfoFlags.NONE, cancellable); 98 | #if GLIB_2_62 99 | last_modified = info.get_modification_date_time (); 100 | #else 101 | TimeVal time_last_modified = info.get_modification_time (); 102 | last_modified = new DateTime.from_iso8601 (time_last_modified.to_iso8601 (), null); 103 | #endif 104 | file_exists = true; 105 | } catch (IOError.NOT_FOUND e) { 106 | // we only want to catch file-not-found errors. if there was some other error 107 | // with querying the file system, we want to exit this function 108 | } 109 | 110 | if (file_exists && last_modified == null) 111 | warning ("could not get last modified time of %s", file.get_uri ()); 112 | 113 | if (status == null) { 114 | // the file is being entered into the cache for the first time 115 | if (file_exists) 116 | _content_cache[file] = new ContentStatus (file.load_bytes (cancellable), last_modified); 117 | else 118 | _content_cache[file] = new ContentStatus.empty (); 119 | return; 120 | } 121 | 122 | // the file is in the cache already. 123 | // check modification time to avoid having to recompute the hash 124 | if (last_modified != null && status.file_last_updated != null && last_modified.compare (status.file_last_updated) <= 0) 125 | return; 126 | 127 | // recompute the hash 128 | ContentStatus new_status; 129 | if (file_exists) 130 | new_status = new ContentStatus (file.load_bytes (cancellable), last_modified); 131 | else 132 | new_status = new ContentStatus.empty (); 133 | if (new_status.checksum == status.checksum && new_status.size == status.size) 134 | return; 135 | 136 | _content_cache[file] = new_status; 137 | return; 138 | } 139 | 140 | /** 141 | * Gets the content status of the file if it's in the cache. 142 | */ 143 | public new ContentStatus? get (File file) { 144 | return _content_cache[file]; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/analysis/codestyleanalyzer.vala: -------------------------------------------------------------------------------- 1 | /* codestyleanalyzer.vala 2 | * 3 | * Copyright 2021-2022 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Vala; 20 | 21 | /** 22 | * Collects statistics on code style in a document. 23 | */ 24 | class Vls.CodeStyleAnalyzer : CodeVisitor, CodeAnalyzer { 25 | private SourceFile? current_file; 26 | private uint _total_spacing; 27 | private uint _num_callable; 28 | 29 | public override DateTime last_updated { get; set; } 30 | 31 | /** 32 | * Average spacing before parentheses in method and delegate declarations. 33 | */ 34 | public uint average_spacing_before_parens { 35 | get { 36 | if (_num_callable == 0) 37 | return 1; 38 | return (_total_spacing + _num_callable / 2) / _num_callable; 39 | } 40 | } 41 | 42 | public CodeStyleAnalyzer (SourceFile source_file) { 43 | this.visit_source_file (source_file); 44 | } 45 | 46 | /** 47 | * Get the indentation of a statement or symbol at a given nesting level. At nesting 48 | * level 0, gets the indentation of the statement. At level 1, gets the 49 | * indentation of a statement inside this statement, and so on... 50 | * 51 | * @param stmt_or_sym a statement or symbol 52 | * @param nesting_level the nesting level inside the statement 53 | * 54 | * @return an empty string if indentation couldn't be determined 55 | */ 56 | public string get_indentation (CodeNode stmt_or_sym, uint nesting_level = 0) 57 | requires (stmt_or_sym is Statement || stmt_or_sym is Symbol) 58 | { 59 | if (stmt_or_sym.source_reference == null) 60 | return ""; 61 | 62 | var source = stmt_or_sym.source_reference; 63 | var parent = stmt_or_sym.parent_node; 64 | 65 | // refine the parent to something better 66 | if (parent is Block) 67 | parent = parent.parent_node; 68 | 69 | // use the indentation within the statement's parent to determine the prefix 70 | int coldiff = source.begin.column; 71 | if ((parent is Statement || parent is Symbol) && parent.source_reference != null) 72 | coldiff -= parent.source_reference.begin.column; 73 | 74 | // walk back from the statement/symbol to the next newline 75 | var offset = (long)((source.begin.pos - (char *)source.file.content) - 1); 76 | 77 | // keep track of last indent and outer indent 78 | var suffix = new StringBuilder (); 79 | var indent = new StringBuilder (); 80 | for (int col = coldiff; offset > 0; --col, --offset) { 81 | char c = stmt_or_sym.source_reference.file.content[offset]; 82 | if (Util.is_newline (c) || !c.isspace ()) 83 | break; 84 | if (col > 0) 85 | suffix.prepend_c (c); 86 | else 87 | indent.prepend_c (c); 88 | } 89 | 90 | for (uint l = 0; l <= nesting_level; ++l) { 91 | if (l > 0 && suffix.len == 0) 92 | // default is 4 spaces (TODO: analyze file / use uncrustify config) 93 | indent.append (" "); 94 | else 95 | indent.append (suffix.str); 96 | } 97 | return indent.str; 98 | } 99 | 100 | public override void visit_source_file (SourceFile source_file) { 101 | current_file = source_file; 102 | source_file.accept_children (this); 103 | current_file = null; 104 | } 105 | 106 | public override void visit_namespace (Namespace ns) { 107 | if (ns.source_reference != null && ns.source_reference.file != current_file) 108 | return; 109 | ns.accept_children (this); 110 | } 111 | 112 | public override void visit_class (Class cl) { 113 | if (cl.source_reference == null || cl.source_reference.file != current_file) 114 | return; 115 | cl.accept_children (this); 116 | } 117 | 118 | public override void visit_interface (Interface iface) { 119 | if (iface.source_reference == null || iface.source_reference.file != current_file) 120 | return; 121 | iface.accept_children (this); 122 | } 123 | 124 | public override void visit_enum (Enum en) { 125 | if (en.source_reference == null || en.source_reference.file != current_file) 126 | return; 127 | en.accept_children (this); 128 | } 129 | 130 | public override void visit_struct (Struct st) { 131 | if (st.source_reference == null || st.source_reference.file != current_file) 132 | return; 133 | st.accept_children (this); 134 | } 135 | 136 | private void analyze_callable (Callable callable) { 137 | _num_callable++; 138 | 139 | // because we allow content to be temporarily inconsistent with the 140 | // parse tree (to allow for fast code completion), we have to use 141 | // [last_fresh_content] 142 | unowned var content = (current_file is TextDocument) ? 143 | ((TextDocument)current_file).last_fresh_content : current_file.content; 144 | var sr = callable.source_reference; 145 | var zero_idx = (long) Util.get_string_pos (content, sr.end.line - 1, sr.end.column); 146 | unowned string text = content.offset (zero_idx); 147 | var spaces = 0; 148 | unichar c = '\0'; 149 | for (var i = 0; text.get_next_char (ref i, out c) && c != '(' && !(c == '\r' || c == '\n');) 150 | spaces++; 151 | if (c == '\r' || c == '\n') 152 | spaces = 1; 153 | _total_spacing += spaces; 154 | } 155 | 156 | public override void visit_delegate (Delegate d) { 157 | if (d.source_reference == null || d.source_reference.file != current_file || 158 | d.source_reference.begin.pos == null) 159 | return; 160 | analyze_callable (d); 161 | } 162 | 163 | public override void visit_method (Method m) { 164 | if (m.source_reference == null || m.source_reference.file != current_file || m.source_reference.begin.pos == null) 165 | return; 166 | analyze_callable (m); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/codehelp/callhierarchy.vala: -------------------------------------------------------------------------------- 1 | /* callhierarchy.vala 2 | * 3 | * Copyright 2022 Princeton Ferro 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Vala; 22 | using Lsp; 23 | 24 | namespace Vls.CallHierarchy { 25 | Symbol? get_containing_sub_or_callable (CodeNode code_node) { 26 | for (var current_node = code_node.parent_node; current_node != null; current_node = current_node.parent_node) { 27 | if (current_node is Subroutine || current_node is Callable) 28 | return (Symbol)current_node; 29 | } 30 | return null; 31 | } 32 | 33 | CallHierarchyIncomingCall[] get_incoming_calls (Project project, Symbol callable) { 34 | var incoming_calls = new Gee.HashMap> (); 35 | Symbol[] symbols = {callable}; 36 | if (callable is Method) { 37 | var method = (Method)callable; 38 | if (method.base_interface_method != method && method.base_interface_method != null) 39 | symbols += method.base_interface_method; 40 | else if (method.base_method != method && method.base_method != null) 41 | symbols += method.base_method; 42 | } 43 | // find all references to this callable 44 | var references = new Gee.HashMap (); 45 | foreach (var symbol in symbols) 46 | foreach (var pair in SymbolReferences.get_compilations_using_symbol (project, symbol)) 47 | foreach (SourceFile file in pair.first.code_context.get_source_files ()) 48 | SymbolReferences.list_in_file (file, pair.second, false, true, references); 49 | debug ("got %d references as incoming calls to %s (%s)", references.size, callable.to_string (), callable.type_name); 50 | foreach (var reference in references) { 51 | if (!(reference.value.parent_node is MethodCall || reference.value.parent_node is ObjectCreationExpression)) 52 | continue; 53 | var container = get_containing_sub_or_callable (reference.value); 54 | if (container != null) { 55 | Gee.ArrayList ranges; 56 | if (!incoming_calls.has_key (container)) { 57 | ranges = new Gee.ArrayList (); 58 | incoming_calls[container] = ranges; 59 | } else { 60 | ranges = incoming_calls[container]; 61 | } 62 | ranges.add (reference.key); 63 | } 64 | } 65 | if (callable is Constructor) { 66 | var ctor = (Constructor)callable; 67 | if (ctor.this_parameter != null && ctor.this_parameter.variable_type is ObjectType) { 68 | var type_symbol = ((ObjectType)ctor.this_parameter.variable_type).object_type_symbol; 69 | foreach (var member in type_symbol.get_members ()) { 70 | if (member is CreationMethod) { 71 | var cm = (CreationMethod)member; 72 | incoming_calls[cm] = new Gee.ArrayList.wrap ({new Range.from_sourceref (member.source_reference ?? type_symbol.source_reference)}); 73 | } 74 | } 75 | } 76 | } 77 | CallHierarchyIncomingCall[] incoming = {}; 78 | foreach (var item in incoming_calls) { 79 | incoming += new CallHierarchyIncomingCall () { 80 | from = new CallHierarchyItem.from_symbol (item.key), 81 | fromRanges = item.value 82 | }; 83 | } 84 | return incoming; 85 | } 86 | 87 | CallHierarchyOutgoingCall[] get_outgoing_calls (Project project, Subroutine subroutine) { 88 | var outgoing_calls = new Gee.HashMap> (); 89 | Subroutine[] subroutines = {subroutine}; 90 | // add all implementing symbols 91 | foreach (var pair in SymbolReferences.get_compilations_using_symbol (project, subroutine)) { 92 | var references = new Gee.HashMap (); 93 | foreach (SourceFile file in pair.first.code_context.get_source_files ()) 94 | SymbolReferences.list_implementations_of_virtual_symbol (file, pair.second, references); 95 | foreach (var node in references.values) 96 | if (node is Vala.Method) 97 | subroutines += (Vala.Method)node; 98 | } 99 | // find all methods that are called in this method 100 | foreach (var current_sub in subroutines) { 101 | if (current_sub.source_reference != null && current_sub.body != null) { 102 | var finder = new NodeSearch.with_filter (current_sub.source_reference.file, current_sub, 103 | (needle, node) => (node is MethodCall || node is ObjectCreationExpression) 104 | && get_containing_sub_or_callable (node) == needle); 105 | var result = new Gee.ArrayList (); 106 | result.add_all (finder.result); 107 | foreach (var node in result) { 108 | var call = (node is MethodCall) ? ((MethodCall)node).call : ((ObjectCreationExpression)node).member_name; 109 | if (node.source_reference == null || call.symbol_reference.source_reference == null) 110 | continue; 111 | var called_item = SymbolReferences.find_real_symbol (project, call.symbol_reference); 112 | Gee.ArrayList ranges; 113 | if (!outgoing_calls.has_key (called_item)) { 114 | ranges = new Gee.ArrayList (); 115 | outgoing_calls[called_item] = ranges; 116 | } else { 117 | ranges = outgoing_calls[called_item]; 118 | } 119 | ranges.add (new Range.from_sourceref (node.source_reference)); 120 | } 121 | } 122 | } 123 | CallHierarchyOutgoingCall[] outgoing = {}; 124 | foreach (var item in outgoing_calls) { 125 | outgoing += new CallHierarchyOutgoingCall () { 126 | to = new CallHierarchyItem.from_symbol (item.key), 127 | fromRanges = item.value 128 | }; 129 | } 130 | return outgoing; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/analysis/inlayhintnodes.vala: -------------------------------------------------------------------------------- 1 | /* inlayhintnodes.vala 2 | * 3 | * Copyright 2022 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | using Vala; 19 | 20 | /** 21 | * Collects all special nodes of interest to the inlay hinter. This must be run 22 | * before the semantic analyzer. 23 | */ 24 | class Vls.InlayHintNodes : Vala.CodeVisitor { 25 | SourceFile? file; 26 | Gee.HashSet declarations; 27 | Gee.HashMap method_calls; 28 | 29 | public InlayHintNodes (Gee.HashSet declarations, Gee.HashMap method_calls) { 30 | this.declarations = declarations; 31 | this.method_calls = method_calls; 32 | } 33 | 34 | public override void visit_source_file (SourceFile source_file) { 35 | file = source_file; 36 | source_file.accept_children (this); 37 | file = null; 38 | } 39 | 40 | public override void visit_namespace (Namespace ns) { 41 | if (ns.source_reference == null || ns.source_reference.file != file) 42 | return; 43 | ns.accept_children (this); 44 | } 45 | 46 | public override void visit_class (Vala.Class cl) { 47 | if (cl.source_reference == null || cl.source_reference.file != file) 48 | return; 49 | cl.accept_children (this); 50 | } 51 | 52 | public override void visit_interface (Vala.Interface iface) { 53 | if (iface.source_reference == null || iface.source_reference.file != file) 54 | return; 55 | iface.accept_children (this); 56 | } 57 | 58 | public override void visit_struct (Vala.Struct st) { 59 | if (st.source_reference == null || st.source_reference.file != file) 60 | return; 61 | st.accept_children (this); 62 | } 63 | 64 | public override void visit_enum (Vala.Enum en) { 65 | if (en.source_reference == null || en.source_reference.file != file) 66 | return; 67 | en.accept_children (this); 68 | } 69 | 70 | public override void visit_method (Method m) { 71 | if (m.source_reference == null || m.source_reference.file != file) 72 | return; 73 | m.accept_children (this); 74 | } 75 | 76 | public override void visit_creation_method (Vala.CreationMethod m) { 77 | if (m.source_reference == null || m.source_reference.file != file) 78 | return; 79 | m.accept_children (this); 80 | } 81 | 82 | public override void visit_destructor (Vala.Destructor d) { 83 | if (d.source_reference == null || d.source_reference.file != file) 84 | return; 85 | d.accept_children (this); 86 | } 87 | 88 | public override void visit_constructor (Vala.Constructor c) { 89 | if (c.source_reference == null || c.source_reference.file != file) 90 | return; 91 | c.accept_children (this); 92 | } 93 | 94 | public override void visit_signal (Vala.Signal sig) { 95 | sig.accept_children (this); 96 | } 97 | 98 | public override void visit_property (Vala.Property prop) { 99 | prop.accept_children (this); 100 | } 101 | 102 | public override void visit_property_accessor (Vala.PropertyAccessor acc) { 103 | acc.accept_children (this); 104 | } 105 | 106 | public override void visit_block (Block b) { 107 | b.accept_children (this); 108 | } 109 | 110 | public override void visit_declaration_statement (Vala.DeclarationStatement stmt) { 111 | stmt.accept_children (this); 112 | } 113 | 114 | public override void visit_local_variable (LocalVariable local) { 115 | if (!(local.initializer is CastExpression || local.initializer is ObjectCreationExpression || local.initializer is ArrayCreationExpression) && 116 | local.variable_type is VarType) 117 | declarations.add (local); 118 | local.accept_children (this); 119 | } 120 | 121 | public override void visit_expression_statement (Vala.ExpressionStatement stmt) { 122 | stmt.accept_children (this); 123 | } 124 | 125 | public override void visit_expression (Expression expr) { 126 | expr.accept_children (this); 127 | } 128 | 129 | public override void visit_method_call (Vala.MethodCall expr) { 130 | method_calls[expr] = expr.get_argument_list ().size; 131 | } 132 | 133 | public override void visit_object_creation_expression (Vala.ObjectCreationExpression expr) { 134 | method_calls[expr] = expr.get_argument_list ().size; 135 | } 136 | 137 | public override void visit_formal_parameter (Vala.Parameter p) { 138 | p.accept_children (this); 139 | } 140 | 141 | public override void visit_if_statement (IfStatement stmt) { 142 | stmt.accept_children (this); 143 | } 144 | 145 | #if VALA_0_52 146 | public override void visit_loop_statement (LoopStatement stmt) { 147 | #else 148 | public override void visit_loop (Loop stmt) { 149 | #endif 150 | stmt.accept_children (this); 151 | } 152 | 153 | public override void visit_for_statement (ForStatement stmt) { 154 | stmt.accept_children (this); 155 | } 156 | 157 | public override void visit_foreach_statement (Vala.ForeachStatement stmt) { 158 | stmt.accept_children (this); 159 | } 160 | 161 | public override void visit_do_statement (Vala.DoStatement stmt) { 162 | stmt.accept_children (this); 163 | } 164 | 165 | public override void visit_while_statement (WhileStatement stmt) { 166 | stmt.accept_children (this); 167 | } 168 | 169 | public override void visit_try_statement (TryStatement stmt) { 170 | stmt.accept_children (this); 171 | } 172 | 173 | public override void visit_catch_clause (CatchClause clause) { 174 | clause.accept_children (this); 175 | } 176 | 177 | public override void visit_return_statement (ReturnStatement stmt) { 178 | stmt.accept_children (this); 179 | } 180 | 181 | public override void visit_yield_statement (YieldStatement stmt) { 182 | stmt.accept_children (this); 183 | } 184 | 185 | public override void visit_lock_statement (LockStatement stmt) { 186 | stmt.accept_children (this); 187 | } 188 | 189 | public override void visit_unlock_statement (UnlockStatement stmt) { 190 | stmt.accept_children (this); 191 | } 192 | 193 | public override void visit_switch_statement (SwitchStatement stmt) { 194 | stmt.accept_children (this); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/projects/types.vala: -------------------------------------------------------------------------------- 1 | /* types.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | namespace Vls { 22 | class CompileCommand : Object, Json.Serializable { 23 | public string directory { get; set; } 24 | public string[] command { get; set; } 25 | public string file { get; set; } 26 | public string output { get; set; } 27 | 28 | public new void Json.Serializable.set_property (ParamSpec pspec, Value value) { 29 | base.set_property (pspec.get_name (), value); 30 | } 31 | 32 | public new Value Json.Serializable.get_property (ParamSpec pspec) { 33 | Value val = Value(pspec.value_type); 34 | base.get_property (pspec.get_name (), ref val); 35 | return val; 36 | } 37 | 38 | public unowned ParamSpec? find_property (string name) { 39 | return this.get_class ().find_property (name); 40 | } 41 | 42 | public Json.Node serialize_property (string property_name, Value value, ParamSpec pspec) { 43 | error ("MesonTarget: serialization not supported"); 44 | } 45 | 46 | public bool deserialize_property (string property_name, out Value val, ParamSpec pspec, Json.Node property_node) { 47 | if (property_name == "directory" || 48 | property_name == "file" || 49 | property_name == "output") { 50 | val = Value (typeof (string)); 51 | val.set_string (property_node.get_string ()); 52 | return true; 53 | } else if (property_name == "command") { 54 | val = Value (typeof (string[])); 55 | string[] command_array = {}; 56 | try { 57 | string? command_str = property_node.get_string (); 58 | if (command_str != null) 59 | command_array = Util.get_arguments_from_command_str (property_node.get_string ()); 60 | } catch (RegexError e) { 61 | warning ("failed to parse `%s': %s", property_node.get_string (), e.message); 62 | } 63 | val.set_boxed (command_array); 64 | return true; 65 | } 66 | val = Value (pspec.value_type); 67 | return false; 68 | } 69 | } 70 | 71 | class Pair : Object { 72 | public T first { get; private set; } 73 | public U second { get; private set; } 74 | 75 | public Pair (T first, U second) { 76 | this.first = first; 77 | this.second = second; 78 | } 79 | } 80 | } 81 | 82 | namespace Meson { 83 | class TargetSourceInfo : Object { 84 | public string language { get; set; } 85 | public string[] compiler { get; set; } 86 | public string[] parameters { get; set; } 87 | public string[] sources { get; set; } 88 | /** 89 | * Like sources, but comes from files generated by other targets. 90 | */ 91 | public string[] generated_sources { get; set; } 92 | } 93 | 94 | class TargetInfo : Object, Json.Serializable { 95 | public string name { get; set; } 96 | public string id { get; set; } 97 | // Vala codegen forbids having 'type' as a property 98 | public string target_type { get; set; } 99 | public string defined_in { get; set; } 100 | public string?[] filename { get; set; } 101 | public ArrayList target_sources { get; set; } 102 | 103 | public new void Json.Serializable.set_property (ParamSpec pspec, Value value) { 104 | base.set_property (pspec.get_name (), value); 105 | } 106 | 107 | public new Value Json.Serializable.get_property (ParamSpec pspec) { 108 | Value val = Value(pspec.value_type); 109 | base.get_property (pspec.get_name (), ref val); 110 | return val; 111 | } 112 | 113 | public unowned ParamSpec? find_property (string name) { 114 | if (name == "type") 115 | return this.get_class ().find_property ("target_type"); 116 | return this.get_class ().find_property (name); 117 | } 118 | 119 | public Json.Node serialize_property (string property_name, Value value, ParamSpec pspec) { 120 | error ("MesonTarget: serialization not supported"); 121 | } 122 | 123 | public bool deserialize_property (string property_name, out Value val, ParamSpec pspec, Json.Node property_node) { 124 | if (property_name == "name" || 125 | property_name == "id" || 126 | property_name == "type" || 127 | property_name == "defined-in") { 128 | val = Value (typeof (string)); 129 | val.set_string (property_node.get_string ()); 130 | return true; 131 | } else if (property_name == "filename") { 132 | val = Value (typeof (string[])); 133 | var array = new string [property_node.get_array ().get_length ()]; 134 | property_node.get_array ().foreach_element ((json_array, i, node) => { 135 | array[i] = node.get_string (); 136 | }); 137 | val.set_boxed (array); 138 | return true; 139 | } else if (property_name == "target-sources") { 140 | target_sources = new ArrayList (); 141 | property_node.get_array ().foreach_element ((_1, _2, node) => { 142 | Json.Node? language_property = node.get_object ().get_member ("language"); 143 | if (language_property == null) 144 | return; 145 | var tsi = Json.gobject_deserialize (typeof (Meson.TargetSourceInfo), node) as Meson.TargetSourceInfo?; 146 | assert (tsi != null); 147 | target_sources.add (tsi); 148 | }); 149 | val = Value (target_sources.get_type ()); 150 | val.set_object (target_sources); 151 | return true; 152 | } 153 | val = Value (pspec.value_type); 154 | return false; 155 | } 156 | } 157 | 158 | /** 159 | * can be found in `$BUILD_ROOT/meson-info/intro-dependencies.json` 160 | */ 161 | class Dependency : Object { 162 | public string name { get; set; } 163 | public string version { get; set; } 164 | public string[] compile_args { get; set; } 165 | public string[] link_args { get; set; } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /test/testclient.vala: -------------------------------------------------------------------------------- 1 | /* testclient.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | class Vls.TestClient : Jsonrpc.Server { 22 | private static HashSet instances = new HashSet (); 23 | private Jsonrpc.Client? vls_jsonrpc_client; 24 | private Subprocess vls_subprocess; 25 | private SubprocessLauncher launcher; 26 | private IOStream subprocess_stream; 27 | 28 | public string root_path { get; private set; } 29 | 30 | static construct { 31 | Posix.@signal (Posix.Signal.INT, () => { 32 | foreach (var client in instances) 33 | client.shutdown (); 34 | }); 35 | } 36 | 37 | ~TestClient () { 38 | TestClient.instances.remove (this); 39 | } 40 | 41 | public TestClient (string server_location, string root_path, string[] env_vars, bool unset_env) throws Error { 42 | TestClient.instances.add (this); 43 | 44 | Log.set_handler (null, LogLevelFlags.LEVEL_MASK, log_handler); 45 | Log.set_handler ("jsonrpc-server", LogLevelFlags.LEVEL_MASK, log_handler); 46 | 47 | this.root_path = root_path; 48 | this.launcher = new SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE); 49 | 50 | if (unset_env) 51 | launcher.set_environ (new string[]{}); 52 | foreach (string env in env_vars) { 53 | int p = env.index_of_char ('='); 54 | if (p == -1) 55 | throw new IOError.INVALID_ARGUMENT ("`%s' not of the form VAR=STRING", env); 56 | launcher.setenv (env[0:p], env.substring (p+1), true); 57 | } 58 | 59 | vls_subprocess = launcher.spawnv ({server_location}); 60 | 61 | if (pause_for_debugger) { 62 | print ("Attach debugger to PID %s and press enter.\n", vls_subprocess.get_identifier ()); 63 | stdin.read_line (); 64 | } 65 | 66 | var input_stream = vls_subprocess.get_stdout_pipe (); 67 | var output_stream = vls_subprocess.get_stdin_pipe (); 68 | 69 | #if !WINDOWS 70 | if (input_stream is UnixInputStream && output_stream is UnixOutputStream) { 71 | // set nonblocking 72 | if (!Unix.set_fd_nonblocking (((UnixInputStream)input_stream).fd, true) 73 | || !Unix.set_fd_nonblocking (((UnixOutputStream)output_stream).fd, true)) 74 | error ("could not set pipes to nonblocking.\n"); 75 | } 76 | #endif 77 | 78 | this.subprocess_stream = new SimpleIOStream (input_stream, output_stream); 79 | accept_io_stream (subprocess_stream); 80 | } 81 | 82 | private void log_handler (string? log_domain, LogLevelFlags log_levels, string message) { 83 | stderr.printf ("%s: %s\n", log_domain == null ? "vls-testclient" : log_domain, message); 84 | } 85 | 86 | // a{sv} only 87 | public Variant buildDict (...) { 88 | var builder = new VariantBuilder (new VariantType ("a{sv}")); 89 | var l = va_list (); 90 | while (true) { 91 | string? key = l.arg (); 92 | if (key == null) { 93 | break; 94 | } 95 | Variant val = l.arg (); 96 | builder.add ("{sv}", key, val); 97 | } 98 | return builder.end (); 99 | } 100 | 101 | public override void client_accepted (Jsonrpc.Client client) { 102 | if (vls_jsonrpc_client == null) { 103 | vls_jsonrpc_client = client; 104 | try { 105 | initialize_server (); 106 | } catch (Error e) { 107 | try { 108 | printerr ("failed to initialize server: %s", e.message); 109 | client.close (); 110 | } catch (Error e) {} 111 | } 112 | } 113 | } 114 | 115 | #if WITH_JSONRPC_GLIB_3_30 116 | public override void client_closed (Jsonrpc.Client client) { 117 | if (client == vls_jsonrpc_client) { 118 | vls_jsonrpc_client = null; 119 | } 120 | } 121 | #endif 122 | 123 | private void initialize_server () throws Error { 124 | Variant? return_value; 125 | vls_jsonrpc_client.call ( 126 | "initialize", 127 | buildDict ( 128 | processId: new Variant.int32 ((int32) Posix.getpid ()), 129 | rootPath: new Variant.string (root_path), 130 | rootUri: new Variant.string (File.new_for_path (root_path).get_uri ()) 131 | ), 132 | null, 133 | out return_value 134 | ); 135 | debug ("VLS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); 136 | } 137 | 138 | public void wait_for_server () throws Error { 139 | vls_subprocess.wait (); 140 | } 141 | 142 | public override void notification (Jsonrpc.Client client, string method, Variant @params) { 143 | debug ("VLS sent notification `%s': %s", method, Json.to_string (Json.gvariant_serialize (@params), true)); 144 | } 145 | 146 | public void shutdown () { 147 | try { 148 | subprocess_stream.close (); 149 | debug ("closed subprocess stream"); 150 | } catch (Error e) {} 151 | } 152 | } 153 | 154 | string? server_location; 155 | [CCode (array_length = false, array_null_terminated = true)] 156 | string[]? env_vars; 157 | string? root_path; 158 | bool unset_env; 159 | bool pause_for_debugger = false; 160 | const OptionEntry[] options = { 161 | { "server", 's', 0, OptionArg.FILENAME, ref server_location, "Location of server binary", "FILE" }, 162 | { "root-path", 'r', 0, OptionArg.FILENAME, ref root_path, "Root path to initialize VLS in", "DIRECTORY" }, 163 | { "environ", 'e', 0, OptionArg.STRING_ARRAY, ref env_vars, "List of environment variables", null }, 164 | { "unset-environment", 'u', 0, OptionArg.NONE, ref unset_env, "Don't inherit parent environment", null }, 165 | { "pause", 'p', 0, OptionArg.NONE, ref pause_for_debugger, "Pause before calling VLS to get a chance to attach a debugger", null }, 166 | { null } 167 | }; 168 | 169 | int main (string[] args) { 170 | try { 171 | var opt_context = new OptionContext ("- VLS Test Client"); 172 | opt_context.set_help_enabled (true); 173 | opt_context.add_main_entries (options, null); 174 | opt_context.parse (ref args); 175 | } catch (OptionError e) { 176 | printerr ("error: %s\n", e.message); 177 | printerr ("Run '%s --help'\n", args[0]); 178 | return 1; 179 | } 180 | 181 | if (server_location == null) { 182 | printerr ("server location required\n"); 183 | return 1; 184 | } 185 | 186 | if (root_path == null) { 187 | printerr ("root path required\n"); 188 | return 1; 189 | } 190 | 191 | try { 192 | var client = new Vls.TestClient (server_location, root_path, env_vars, unset_env); 193 | client.wait_for_server (); 194 | } catch (Error e) { 195 | printerr ("error running test client: %s\n", e.message); 196 | return 1; 197 | } 198 | 199 | return 0; 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vala Language Server 2 | 3 | This is a language server for the [Vala programming language](https://vala-project.org). 4 | 5 | ## Installation 6 | 7 | We recommend using VSCode with the [Vala plugin](https://marketplace.visualstudio.com/items?itemName=prince781.vala). 8 | 9 | - Arch Linux (via AUR): `yay -S vala-language-server` 10 | or `yay -S vala-language-server-git` 11 | 12 | - Ubuntu, Fedora, Debian, openSUSE, and Mageia: install from [the OBS repo](https://software.opensuse.org//download.html?project=home%3APrince781&package=vala-language-server) [![build result](https://build.opensuse.org/projects/home:Prince781/packages/vala-language-server/badge.svg?type=percent)](https://build.opensuse.org/package/show/home:Prince781/vala-language-server) 13 | 14 | - Fedora (official): `sudo dnf install vala-language-server` 15 | 16 | - elementaryOS: `sudo apt install vala-language-server` 17 | 18 | - Alpine Linux: `apk add vala-language-server` 19 | 20 | - Guix: `guix install vala-language-server` 21 | 22 | - Void Linux: `xbps-install vala-language-server` 23 | 24 | - Windows (via MSYS2): `pacman -S mingw-w64-x86_64-vala-language-server` 25 | 26 | ...and more. See below: 27 | 28 | [![Packaging status](https://repology.org/badge/vertical-allrepos/vala-language-server.svg)](https://repology.org/project/vala-language-server/versions) 29 | 30 | ![VLS with VSCode](images/vls-vscode.png) 31 | ![VLS with Vim with coc.nvim and vista plugins](images/vls-vim.png) 32 | ![VLS with GNOME Builder](images/vls-gb.png) 33 | 34 | ## Table of Contents 35 | - [Vala Language Server](#vala-language-server) 36 | - [Table of Contents](#table-of-contents) 37 | - [Features](#features) 38 | - [Dependencies](#dependencies) 39 | - [Building from Source](#building-from-source) 40 | - [Editors](#editors) 41 | - [Vim and Neovim](#vim-and-neovim) 42 | - [Visual Studio Code](#visual-studio-code) 43 | - [GNOME Builder](#gnome-builder) 44 | - [Kate](#kate) 45 | - [Emacs](#emacs) 46 | - [Sublime Text](#sublime-text) 47 | - [Contributing](#contributing) 48 | 49 | ## Features 50 | - [x] diagnostics 51 | - [x] code completion 52 | - [x] member access and scope-visible completion 53 | - [x] context-sensitive suggestions 54 | - completions for abstract methods/properties to implement 55 | - [x] symbol outline 56 | - [x] goto definition 57 | - [x] symbol references 58 | - [x] goto implementation 59 | - [x] signature help 60 | - [x] hover 61 | - [x] symbol documentation 62 | - [x] from comments in source code 63 | - [x] from GIR and VAPI files 64 | - [x] search for symbols in workspace 65 | - [x] highlight active symbol in document 66 | - [x] rename 67 | - [x] snippets 68 | - for implementing abstract methods/properties 69 | - [x] code lenses 70 | - [x] code actions / quick fixes 71 | - [x] code formatting 72 | - [x] call hierarchy 73 | - [x] inlay hints 74 | - [ ] workspaces 75 | - [ ] supported projects 76 | - [x] meson 77 | - [x] `compile_commands.json` 78 | - [x] Vala script (file beginning with `#!/usr/bin/env -S vala` shebang) 79 | - [ ] cmake 80 | - [ ] autotools 81 | 82 | ## Dependencies 83 | - `glib-2.0` 84 | - `gobject-2.0` 85 | - `gio-2.0` and either `gio-unix-2.0` or `gio-windows-2.0` 86 | - `gee-0.8` 87 | - `json-glib-1.0` 88 | - `jsonrpc-glib-1.0 >= 3.28` 89 | - `libvala >= 0.48.12` 90 | - you also need the `posix` VAPI, which should come preinstalled 91 | - [uncrustify](http://uncrustify.sourceforge.net/), for formatting (optional) 92 | 93 | #### Install dependencies with Guix 94 | 95 | If you're using Guix, to launch a shell with build dependencies satisfied: 96 | ```sh 97 | guix environment vala-language-server 98 | ``` 99 | 100 | ## Building from Source 101 | ```sh 102 | meson -Dprefix=/usr build 103 | ninja -C build 104 | sudo ninja -C build install 105 | ``` 106 | 107 | This will install `vala-language-server` to `/usr/bin` 108 | 109 | ## Editors 110 | 111 | **An important note**: VLS cannot know what arguments are used for the file you are editing unless it can locate a build script, compile commands list, or shebang to analyze. (This is generally true for most language servers of compiled languages.) Before making an issue, check whether you have a build script or shebang for your file. 112 | 113 | ### vim and neovim 114 | 115 | #### coc.nvim 116 | 1. Make sure [coc.nvim](https://github.com/neoclide/coc.nvim) is installed. 117 | 2. After successful installation, in Vim run `:CocConfig` and add a new entry 118 | for VLS to the `languageserver` property like below: 119 | 120 | ```json 121 | { 122 | "languageserver": { 123 | "vala": { 124 | "command": "vala-language-server", 125 | "filetypes": ["vala", "genie"] 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | #### vim-lsp 132 | 1. Make sure [vim-lsp](https://github.com/prabirshrestha/vim-lsp) is installed 133 | 2. Add the following to your `.vimrc`: 134 | 135 | ```vim 136 | if executable('vala-language-server') 137 | au User lsp_setup call lsp#register_server({ 138 | \ 'name': 'vala-language-server', 139 | \ 'cmd': {server_info->[&shell, &shellcmdflag, 'vala-language-server']}, 140 | \ 'whitelist': ['vala', 'genie'], 141 | \ }) 142 | endif 143 | ``` 144 | 145 | #### nvim-lspconfig 146 | - For Neovim 0.5.0 or higher 147 | - Make sure [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) is installed 148 | - see [the recommended config](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#vala_ls) in that project 149 | 150 | ```lua 151 | require'lspconfig'.vala_ls.setup { 152 | -- defaults, no need to specify these 153 | cmd = { "vala-language-server" }, 154 | filetypes = { "vala", "genie" }, 155 | root_dir = root_pattern("meson.build", ".git"), 156 | single_file_support = true, 157 | } 158 | ``` 159 | 160 | ### Visual Studio Code 161 | - [Official Vala plugin](https://marketplace.visualstudio.com/items?itemName=prince781.vala) 162 | 163 | ### GNOME Builder 164 | - requires GNOME Builder >= 3.35 165 | - Running `ninja -C build install` should install the third-party plugin to `$PREFIX/lib/gnome-builder/plugins`. Enable `Vala` and disable `GNOME Vala Language Server`. 166 | 167 | ### Kate 168 | - officially supported in Kate git master 169 | - **Warning:** Kate will silently fail to find symbols when meson cannot be found in path without notifying the user. 170 | 171 | ### Emacs 172 | - supported with the [lsp-mode](https://github.com/emacs-lsp/lsp-mode) plugin 173 | 174 | ### Sublime Text 175 | - Install the [Vala-TMBundle](https://packagecontrol.io/packages/Vala-TMBundle) and [LSP](https://github.com/sublimelsp/LSP) packages 176 | - Add this to your `LSP.sublime-settings`: 177 | ```json 178 | { 179 | "clients": { 180 | "vala-language-server": { 181 | "command": [ 182 | "/usr/bin/vala-language-server" 183 | ], 184 | "selector": "source.vala | source.genie", 185 | "enabled": true 186 | } 187 | } 188 | } 189 | ``` 190 | - Run `Tools > LSP > Enable Language Server Globally... > vala-language-server` 191 | 192 | ## Specifying dependencies 193 | 194 | If you're using meson, vala-language-server will automatically detect the packages you depend on. 195 | 196 | If you just want to write a quick script without a build system, you can add a shebang line to the top of your .vala file: 197 | 198 | ```c 199 | #!/usr/bin/env -S vala --pkg gtk4 200 | ``` 201 | 202 | ## Contributing 203 | Want to help out? Here are some helpful resources: 204 | 205 | - [Help is wanted on these issues](https://github.com/vala-lang/vala-language-server/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) 206 | - [`#vala` on gimpnet/IRC](irc://irc.gnome.org/vala) is for general discussions about Vala and collaboration with upstream 207 | - [Discord server](https://discord.gg/YFAzjSVHt7) is for general discussions about Vala, discussions about this project, and support 208 | - Gitter room is also for project discussions and support, but is less active: https://gitter.im/vala-language-server/community 209 | - Vala wiki: https://wiki.gnome.org/Projects/Vala/ 210 | - libvala documentation: https://gnome.pages.gitlab.gnome.org/vala/docs/index.html 211 | -------------------------------------------------------------------------------- /src/codehelp/codeaction.vala: -------------------------------------------------------------------------------- 1 | /* codeaction.vala 2 | * 3 | * Copyright 2022 JCWasmx86 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Lsp; 22 | using Vala; 23 | 24 | namespace Vls.CodeActions { 25 | /** 26 | * Extracts a list of code actions for the given document and range. 27 | * 28 | * @param file the current document 29 | * @param range the range to show code actions for 30 | * @param uri the document URI 31 | */ 32 | Collection extract (CodeActionContext context, Compilation compilation, TextDocument file, Range range, string uri) { 33 | var code_actions = new ArrayList (); 34 | 35 | if (file.last_updated.compare (compilation.last_updated) > 0) 36 | // don't show code actions for a stale document 37 | return code_actions; 38 | 39 | var class_ranges = new HashMap (); 40 | var document = new VersionedTextDocumentIdentifier () { 41 | version = file.version, 42 | uri = uri 43 | }; 44 | 45 | // search for nodes containing the query range 46 | var finder = new NodeSearch (file, range.start, true, range.end, false); 47 | 48 | // add code actions 49 | foreach (CodeNode code_node in finder.result) { 50 | if (code_node is IntegerLiteral) { 51 | var lit = (IntegerLiteral)code_node; 52 | var lit_range = new Range.from_sourceref (lit.source_reference); 53 | if (lit_range.contains (range.start) && lit_range.contains (range.end)) 54 | code_actions.add (new BaseConverterAction (lit, document)); 55 | } else if (code_node is Class) { 56 | var csym = (Class)code_node; 57 | var clsdef_range = compute_class_def_range (csym, class_ranges); 58 | var missing = CodeHelp.gather_missing_prereqs_and_unimplemented_symbols (csym); 59 | if (!missing.first.is_empty || !missing.second.is_empty) { 60 | var code_style = compilation.get_analysis_for_file (file); 61 | code_actions.add (new ImplementMissingPrereqsAction (context, 62 | csym, missing.first, missing.second, 63 | clsdef_range.end, code_style, document)); 64 | } 65 | } else if (code_node is SwitchStatement) { 66 | var sws = (SwitchStatement)code_node; 67 | var expr = sws.expression.target_type; 68 | var labels = sws.get_sections (); 69 | if (expr is EnumValueType) { 70 | var consts_by_name = new Gee.HashSet (); 71 | var evt = (EnumValueType)expr; 72 | var e = evt.type_symbol; 73 | if (!(e is Enum)) { 74 | warning ("enum value type doesn't have enum - %s", evt.to_string ()); 75 | continue; 76 | } 77 | foreach (var ec in ((Enum)e).get_values ()) { 78 | consts_by_name.add (ec.name); 79 | } 80 | var found_default = false; 81 | foreach (var l in labels) { 82 | if (l.has_default_label ()) { 83 | found_default = true; 84 | } 85 | foreach (var a in l.get_labels ()) { 86 | var case_expression = a.expression; 87 | // Default label 88 | if (case_expression == null) 89 | continue; 90 | if (case_expression.symbol_reference is Constant) 91 | consts_by_name.remove (((Constant)case_expression.symbol_reference).name); 92 | } 93 | } 94 | if (found_default && consts_by_name.is_empty) 95 | continue; 96 | var code_style = compilation.get_analysis_for_file (file); 97 | if (!found_default && sws.source_reference != null) 98 | code_actions.add (new AddDefaultToSwitchAction (context, sws, document, code_style)); 99 | if (!consts_by_name.is_empty && sws.source_reference != null) 100 | code_actions.add (new AddOtherConstantsToSwitchAction (context, 101 | sws, document, 102 | (Enum)e, consts_by_name, code_style)); 103 | } else { 104 | var found_default = false; 105 | foreach (var l in labels) { 106 | if (l.has_default_label ()) { 107 | found_default = true; 108 | break; 109 | } 110 | } 111 | if (!found_default && sws.source_reference != null) { 112 | var code_style = compilation.get_analysis_for_file (file); 113 | code_actions.add (new AddDefaultToSwitchAction (context, sws, document, code_style)); 114 | } 115 | } 116 | } 117 | } 118 | 119 | return code_actions; 120 | } 121 | 122 | Position compute_real_symbol_end (Symbol sym, char terminator) { 123 | var pos = new Position.from_libvala (sym.source_reference.end); 124 | var offset = sym.source_reference.end.pos - (char *)sym.source_reference.file.content; 125 | var dl = 0; 126 | var dc = 0; 127 | unowned string? content = sym.source_reference.file.content; 128 | while (offset < content.length && content[(long)offset] != terminator) { 129 | if (Util.is_newline (content[(long)offset])) { 130 | dl++; 131 | dc = 0; 132 | } else { 133 | dc++; 134 | } 135 | offset++; 136 | } 137 | return pos.translate (dl, dc + 1); 138 | } 139 | 140 | /** 141 | * Compute the full range of a class definition. 142 | */ 143 | Range compute_class_def_range (Class csym, Map class_ranges) { 144 | if (csym in class_ranges) 145 | return class_ranges[csym]; 146 | // otherwise compute the result and cache it 147 | // csym.source_reference must be non-null otherwise NodeSearch wouldn't have found csym 148 | var classdef_range = new Range.from_sourceref (csym.source_reference); 149 | var range = classdef_range.union (new Range.from_pos (compute_real_symbol_end (csym, '{'))); 150 | // debug ("csym range: %s -> %s", csym.to_string (), classdef_range.to_string ()); 151 | foreach (Symbol member in csym.get_members ()) { 152 | if (member.source_reference == null) 153 | continue; 154 | if (member is Field) 155 | range = range.union (new Range.from_pos (compute_real_symbol_end (member, ';'))); 156 | if (member is Method && ((Method)member).body != null && ((Method)member).body.source_reference != null) 157 | range = range.union (new Range.from_sourceref (((Method)member).body.source_reference)); 158 | // debug ("expanding range to %s", range.to_string ()); 159 | } 160 | class_ranges[csym] = range; 161 | return range; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/codeaction/implementmissingprereqsaction.vala: -------------------------------------------------------------------------------- 1 | /* implementmissingprereqsaction.vala 2 | * 3 | * Copyright 2022 Princeton Ferro 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Lsp; 22 | using Gee; 23 | 24 | /** 25 | * Implement all missing prerequisites of a class type. 26 | */ 27 | class Vls.ImplementMissingPrereqsAction : CodeAction { 28 | public ImplementMissingPrereqsAction (CodeActionContext context, 29 | Vala.Class class_sym, 30 | Vala.Collection missing_prereqs, 31 | Vala.Collection> missing_symbols, 32 | Position classdef_end, 33 | CodeStyleAnalyzer code_style, 34 | VersionedTextDocumentIdentifier document) { 35 | this.title = "Implement missing prerequisites for class"; 36 | this.kind = "quickfix"; 37 | this.edit = new WorkspaceEdit (); 38 | 39 | var changes = new ArrayList (); 40 | var document_edit = new TextDocumentEdit (document); 41 | changes.add (document_edit); 42 | 43 | // insert the types after the class declaration 44 | var cls_endpos = new Position.from_libvala (class_sym.source_reference.end); 45 | var typelist_text = new StringBuilder (); 46 | var prereq_i = 0; 47 | foreach (var prereq_type in missing_prereqs) { 48 | if (prereq_i > 0) { 49 | typelist_text.append (", "); 50 | } else { 51 | typelist_text.append (CodeHelp.get_code_node_source (class_sym).index_of_char (':') == -1 ? " : " : ", "); 52 | } 53 | typelist_text.append (CodeHelp.get_data_type_representation (prereq_type, class_sym.scope, true)); 54 | prereq_i++; 55 | } 56 | document_edit.edits.add (new TextEdit (new Range () { 57 | start = cls_endpos, 58 | end = cls_endpos 59 | }, typelist_text.str)); 60 | 61 | // insert the methods and properties that need to be implemented 62 | var symbols_insert_text = new StringBuilder (); 63 | string symbol_indent, inner_indent; 64 | var visible_members = CodeHelp.get_visible_members (class_sym); 65 | 66 | if (visible_members.is_empty) { 67 | symbol_indent = code_style.get_indentation (class_sym, 1); 68 | inner_indent = code_style.get_indentation (class_sym, 2); 69 | } else { 70 | var last_member = visible_members.last (); 71 | symbol_indent = code_style.get_indentation (last_member); 72 | inner_indent = code_style.get_indentation (last_member, 1); 73 | } 74 | foreach (var prereq_sym_pair in missing_symbols) { 75 | var instance_type = prereq_sym_pair.first; 76 | var sym = prereq_sym_pair.second; 77 | 78 | if (!(sym is Vala.Method || sym is Vala.Property)) { 79 | warning ("unexpected symbol type %s @ %s", sym.type_name, sym.source_reference.to_string ()); 80 | continue; 81 | } 82 | 83 | symbols_insert_text.append_printf ("\n%s%s ", symbol_indent, sym.access.to_string ()); 84 | 85 | if (sym.hides) 86 | symbols_insert_text.append ("new "); 87 | 88 | if (sym is Vala.Method && ((Vala.Method)sym).coroutine) 89 | symbols_insert_text.append ("async "); 90 | if (sym is Vala.Method && CodeHelp.base_method_requires_override ((Vala.Method)sym) || 91 | sym is Vala.Property && CodeHelp.base_property_requires_override ((Vala.Property)sym)) 92 | symbols_insert_text.append ("override "); 93 | 94 | Vala.DataType? return_type = null; 95 | if (sym is Vala.Callable) 96 | return_type = ((Vala.Callable)sym).return_type.get_actual_type (instance_type, null, null); 97 | else if (sym is Vala.Property) 98 | return_type = ((Vala.Property)sym).property_type.get_actual_type (instance_type, null, null); 99 | 100 | if (return_type != null) { 101 | string? return_type_representation = CodeHelp.get_data_type_representation (return_type, class_sym.scope); 102 | symbols_insert_text.append (return_type_representation); 103 | symbols_insert_text.append_c (' '); 104 | } else { 105 | warning ("no return type for symbol %s", sym.name); 106 | } 107 | 108 | symbols_insert_text.append (sym.name); 109 | 110 | if (sym is Vala.Callable) { 111 | // display type arguments 112 | Vala.List? type_parameters = null; 113 | if (sym is Vala.Delegate) 114 | type_parameters = ((Vala.Delegate)sym).get_type_parameters (); 115 | else if (sym is Vala.Method) 116 | type_parameters = ((Vala.Method)sym).get_type_parameters (); 117 | 118 | if (type_parameters != null && !type_parameters.is_empty) { 119 | symbols_insert_text.append_c ('<'); 120 | int i = 1; 121 | foreach (var type_parameter in type_parameters) { 122 | if (i > 1) { 123 | symbols_insert_text.append_c (','); 124 | } 125 | symbols_insert_text.append (type_parameter.name); 126 | } 127 | symbols_insert_text.append_c ('>'); 128 | } 129 | 130 | symbols_insert_text.append_printf ("%*s(", code_style.average_spacing_before_parens, ""); 131 | 132 | int i = 1; 133 | foreach (Vala.Parameter param in ((Vala.Callable) sym).get_parameters ()) { 134 | if (i > 1) { 135 | symbols_insert_text.append (", "); 136 | } 137 | symbols_insert_text.append (CodeHelp.get_symbol_representation (instance_type, param, class_sym.scope, false)); 138 | i++; 139 | } 140 | symbols_insert_text.append_printf (") {\n%sassert_not_reached%*s();\n%s}", 141 | inner_indent, code_style.average_spacing_before_parens, "", symbol_indent); 142 | } else if (sym is Vala.Property) { 143 | var prop = (Vala.Property)sym; 144 | symbols_insert_text.append (" {"); 145 | if (prop.get_accessor != null) { 146 | if (prop.get_accessor.value_type is Vala.ReferenceType && prop.get_accessor.value_type.value_owned) 147 | symbols_insert_text.append (" owned"); 148 | if (prop.get_accessor.access != prop.access) 149 | symbols_insert_text.append_printf (" %s", prop.get_accessor.access.to_string ()); 150 | symbols_insert_text.append (" get;"); 151 | } 152 | if (prop.set_accessor != null) { 153 | if (prop.set_accessor.value_type is Vala.ReferenceType && prop.set_accessor.value_type.value_owned) 154 | symbols_insert_text.append (" owned"); 155 | if (prop.set_accessor.access != prop.access) 156 | symbols_insert_text.append_printf (" %s", prop.set_accessor.access.to_string ()); 157 | symbols_insert_text.append (" set;"); 158 | } 159 | symbols_insert_text.append (" }"); 160 | } 161 | } 162 | document_edit.edits.add (new TextEdit (new Range () { 163 | start = classdef_end, 164 | end = classdef_end 165 | }, symbols_insert_text.str)); 166 | 167 | this.edit.documentChanges = changes; 168 | 169 | // now, include all relevant diagnostics 170 | foreach (var diag in context.diagnostics) 171 | if (/does not implement|some prerequisites .*are not met/.match (diag.message)) 172 | add_diagnostic (diag); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/codehelp/codelensengine.vala: -------------------------------------------------------------------------------- 1 | /* codelensengine.vala 2 | * 3 | * Copyright 2021 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | using Lsp; 21 | 22 | enum Vls.Command { 23 | /** 24 | * The editor should display the base symbol of a method or property. 25 | */ 26 | EDITOR_SHOW_BASE_SYMBOL, 27 | 28 | /** 29 | * The editor should display the symbol hidden by the current symbol. 30 | */ 31 | EDITOR_SHOW_HIDDEN_SYMBOL; 32 | 33 | public unowned string to_string () { 34 | switch (this) { 35 | case EDITOR_SHOW_BASE_SYMBOL: 36 | return "vala.showBaseSymbol"; 37 | case EDITOR_SHOW_HIDDEN_SYMBOL: 38 | return "vala.showHiddenSymbol"; 39 | } 40 | assert_not_reached (); 41 | } 42 | } 43 | 44 | namespace Vls.CodeLensEngine { 45 | /** 46 | * Represent the symbol in a special way for code lenses: 47 | * `{parent with type parameters}.{symbol_name}` 48 | * 49 | * We don't care to show modifiers, return types, and/or parameters. 50 | */ 51 | string represent_symbol (Vala.Symbol current_symbol, Vala.Symbol target_symbol) { 52 | var builder = new StringBuilder (); 53 | 54 | if (current_symbol.parent_symbol is Vala.TypeSymbol) { 55 | Vala.DataType? target_symbol_parent_type = null; 56 | var ancestor_types = new GLib.Queue (); 57 | ancestor_types.push_tail (Vala.SemanticAnalyzer.get_data_type_for_symbol (current_symbol.parent_symbol)); 58 | 59 | while (target_symbol_parent_type == null && !ancestor_types.is_empty ()) { 60 | var parent_type = ancestor_types.pop_head (); 61 | if (parent_type.type_symbol is Vala.Class) { 62 | foreach (var base_type in ((Vala.Class)parent_type.type_symbol).get_base_types ()) { 63 | var actual_base_type = base_type.get_actual_type (parent_type, null, null); 64 | if (base_type.type_symbol == target_symbol.parent_symbol) { 65 | target_symbol_parent_type = actual_base_type; 66 | break; 67 | } 68 | ancestor_types.push_tail (actual_base_type); 69 | } 70 | } else if (parent_type.type_symbol is Vala.Interface) { 71 | foreach (var base_type in ((Vala.Interface)parent_type.type_symbol).get_prerequisites ()) { 72 | var actual_base_type = base_type.get_actual_type (parent_type, null, null); 73 | if (base_type.type_symbol == target_symbol.parent_symbol) { 74 | target_symbol_parent_type = actual_base_type; 75 | break; 76 | } 77 | ancestor_types.push_tail (actual_base_type); 78 | } 79 | } else if (parent_type.type_symbol is Vala.Struct) { 80 | var base_type = ((Vala.Struct)parent_type.type_symbol).base_type; 81 | var actual_base_type = base_type.get_actual_type (parent_type, null, null); 82 | if (base_type.type_symbol == target_symbol.parent_symbol) { 83 | target_symbol_parent_type = actual_base_type; 84 | break; 85 | } 86 | ancestor_types.push_tail (actual_base_type); 87 | } 88 | } 89 | 90 | builder.append (CodeHelp.get_symbol_representation ( 91 | target_symbol_parent_type, 92 | target_symbol.parent_symbol, 93 | current_symbol.scope, 94 | true, 95 | null, 96 | null, 97 | false, 98 | true)); 99 | 100 | builder.append_c ('.'); 101 | } 102 | 103 | builder.append (target_symbol.name); 104 | if (target_symbol is Vala.Callable) 105 | builder.append ("()"); 106 | return builder.str; 107 | } 108 | 109 | Array create_arguments (Vala.Symbol current_symbol, Vala.Symbol target_symbol) { 110 | var arguments = new Array (); 111 | 112 | try { 113 | arguments.append_val (Util.object_to_variant (new Location.from_sourceref (current_symbol.source_reference))); 114 | arguments.append_val (Util.object_to_variant (new Location.from_sourceref (target_symbol.source_reference))); 115 | } catch (Error e) { 116 | warning ("failed to create arguments for command: %s", e.message); 117 | } 118 | 119 | return arguments; 120 | } 121 | 122 | void begin_response (Server lang_serv, Project project, 123 | Jsonrpc.Client client, Variant id, string method, 124 | Vala.SourceFile doc, Compilation compilation) { 125 | lang_serv.wait_for_context_update (id, request_cancelled => { 126 | if (request_cancelled) { 127 | Server.reply_null (id, client, method); 128 | return; 129 | } 130 | 131 | Vala.CodeContext.push (compilation.code_context); 132 | var collected_symbols = compilation.get_analysis_for_file (doc); 133 | Vala.CodeContext.pop (); 134 | 135 | var lenses = new ArrayList (); 136 | 137 | lenses.add_all_iterator ( 138 | collected_symbols.found_overrides 139 | .map (entry => 140 | new CodeLens () { 141 | range = new Range.from_sourceref (entry.key.source_reference), 142 | command = new Lsp.Command () { 143 | title = "overrides " + represent_symbol (entry.key, entry.value), 144 | command = Command.EDITOR_SHOW_BASE_SYMBOL.to_string (), 145 | arguments = create_arguments (entry.key, entry.value) 146 | } 147 | })); 148 | 149 | lenses.add_all_iterator ( 150 | collected_symbols.found_implementations 151 | .map (entry => 152 | new CodeLens () { 153 | range = new Range.from_sourceref (entry.key.source_reference), 154 | command = new Lsp.Command () { 155 | title = "implements " + represent_symbol (entry.key, entry.value), 156 | command = Command.EDITOR_SHOW_BASE_SYMBOL.to_string (), 157 | arguments = create_arguments (entry.key, entry.value) 158 | } 159 | })); 160 | 161 | lenses.add_all_iterator ( 162 | collected_symbols.found_hides 163 | .map (entry => 164 | new CodeLens () { 165 | range = new Range.from_sourceref (entry.key.source_reference), 166 | command = new Lsp.Command () { 167 | title = "hides " + represent_symbol (entry.key, entry.value), 168 | command = Command.EDITOR_SHOW_HIDDEN_SYMBOL.to_string (), 169 | arguments = create_arguments (entry.key, entry.value) 170 | } 171 | })); 172 | 173 | finish (client, id, method, lenses); 174 | }); 175 | } 176 | 177 | void finish (Jsonrpc.Client client, Variant id, string method, Collection lenses) { 178 | try { 179 | var json_array = new Json.Array (); 180 | 181 | foreach (var lens in lenses) 182 | json_array.add_element (Json.gobject_serialize (lens)); 183 | 184 | Variant variant_array = Json.gvariant_deserialize (new Json.Node.alloc ().init_array (json_array), null); 185 | client.reply (id, variant_array, Server.cancellable); 186 | } catch (Error e) { 187 | warning ("[%s] failed to reply to client: %s", method, e.message); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/codehelp/find_scope.vala: -------------------------------------------------------------------------------- 1 | /* find_scope.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Lsp; 20 | using Gee; 21 | 22 | class Vls.FindScope : Vala.CodeVisitor { 23 | Vala.CodeContext context; 24 | Vala.SourceFile file; 25 | Position pos; 26 | ArrayList candidate_blocks = new ArrayList (); 27 | bool before_context_update; 28 | 29 | private Vala.Symbol _best_block; 30 | 31 | public Vala.Symbol best_block { 32 | get { 33 | if (_best_block == null) 34 | compute_best_block (); 35 | return _best_block; 36 | } 37 | } 38 | 39 | public FindScope (Vala.SourceFile file, Position pos, bool before_context_update = true) { 40 | assert (Vala.CodeContext.get () == file.context); 41 | // debug ("FindScope @ %s", pos.to_string ()); 42 | this.context = file.context; 43 | this.file = file; 44 | this.pos = pos; 45 | this.before_context_update = before_context_update; 46 | this.visit_source_file (file); 47 | } 48 | 49 | void compute_best_block () { 50 | Vala.Symbol smallest_block = context.root; 51 | Range? best_range = smallest_block.source_reference != null ? 52 | new Range.from_sourceref (smallest_block.source_reference) : null; 53 | 54 | foreach (Vala.Symbol block in candidate_blocks) { 55 | var scope_range = new Range.from_sourceref (block.source_reference); 56 | if (best_range == null || 57 | best_range.start.compare_to (scope_range.start) <= 0 && 58 | !(best_range.start.compare_to (scope_range.start) == 0 && scope_range.end.compare_to (best_range.end) == 0)) { 59 | smallest_block = block; 60 | best_range = scope_range; 61 | } 62 | } 63 | 64 | _best_block = smallest_block; 65 | } 66 | 67 | void add_if_matches (Vala.Symbol symbol) { 68 | var sr = symbol.source_reference; 69 | if (sr == null) { 70 | // debug ("node %s has no source reference", node.type_name); 71 | return; 72 | } 73 | 74 | if (sr.file != file) { 75 | return; 76 | } 77 | 78 | if (sr.begin.line > sr.end.line) { 79 | warning (@"wtf vala: $(symbol.type_name): $sr"); 80 | return; 81 | } 82 | 83 | var range = new Range.from_sourceref (sr); 84 | 85 | if (symbol is Vala.TypeSymbol || symbol is Vala.Namespace) { 86 | var symtab = symbol.scope.get_symbol_table (); 87 | if (symtab != null) { 88 | foreach (Vala.Symbol member in symtab.get_values ()) { 89 | if (member.source_reference != null && member.source_reference.file == sr.file) 90 | range = range.union (new Range.from_sourceref (member.source_reference)); 91 | } 92 | } 93 | } 94 | 95 | // compare to range.end.line + 1 if before context update, assuming that 96 | // it's possible the user expanded the current scope 97 | Position new_end = before_context_update ? range.end.translate (2) : range.end; 98 | bool pos_within_start = range.start.compare_to (pos) <= 0; 99 | bool pos_within_end = pos.compare_to (range.end) <= 0 || pos.compare_to (new_end) <= 0; 100 | if (pos_within_start && pos_within_end) { 101 | candidate_blocks.add (symbol); 102 | // debug ("%s (%s, @ %s / %s) added to candidates for %s", 103 | // node.to_string (), node.type_name, node.source_reference.to_string (), range.to_string (), pos.to_string ()); 104 | } else { 105 | // debug ("%s (%s, @ %s / %s) not in candidates for %s", 106 | // node.to_string (), node.type_name, node.source_reference.to_string (), range.to_string (), pos.to_string ()); 107 | } 108 | } 109 | 110 | public override void visit_source_file (Vala.SourceFile file) { 111 | file.accept_children (this); 112 | } 113 | 114 | public override void visit_assignment (Vala.Assignment a) { 115 | a.accept_children (this); 116 | } 117 | 118 | public override void visit_binary_expression (Vala.BinaryExpression expr) { 119 | expr.accept_children (this); 120 | } 121 | 122 | public override void visit_block (Vala.Block b) { 123 | add_if_matches (b); 124 | b.accept_children (this); 125 | } 126 | 127 | public override void visit_catch_clause (Vala.CatchClause clause) { 128 | clause.accept_children (this); 129 | } 130 | 131 | public override void visit_class (Vala.Class cl) { 132 | add_if_matches (cl); 133 | cl.accept_children (this); 134 | } 135 | 136 | public override void visit_conditional_expression (Vala.ConditionalExpression expr) { 137 | expr.accept_children (this); 138 | } 139 | 140 | public override void visit_constructor (Vala.Constructor c) { 141 | add_if_matches (c); 142 | c.accept_children (this); 143 | } 144 | 145 | public override void visit_creation_method (Vala.CreationMethod m) { 146 | add_if_matches (m); 147 | m.accept_children (this); 148 | } 149 | 150 | public override void visit_declaration_statement (Vala.DeclarationStatement stmt) { 151 | stmt.accept_children (this); 152 | } 153 | 154 | public override void visit_destructor (Vala.Destructor d) { 155 | add_if_matches (d); 156 | d.accept_children (this); 157 | } 158 | 159 | public override void visit_do_statement (Vala.DoStatement stmt) { 160 | stmt.accept_children (this); 161 | } 162 | 163 | public override void visit_enum (Vala.Enum en) { 164 | en.accept_children (this); 165 | } 166 | 167 | public override void visit_error_domain (Vala.ErrorDomain edomain) { 168 | edomain.accept_children (this); 169 | } 170 | 171 | public override void visit_expression_statement (Vala.ExpressionStatement stmt) { 172 | stmt.accept_children (this); 173 | } 174 | 175 | public override void visit_for_statement (Vala.ForStatement stmt) { 176 | stmt.accept_children (this); 177 | } 178 | 179 | public override void visit_foreach_statement (Vala.ForeachStatement stmt) { 180 | stmt.accept_children (this); 181 | } 182 | 183 | public override void visit_if_statement (Vala.IfStatement stmt) { 184 | stmt.accept_children (this); 185 | } 186 | 187 | public override void visit_interface (Vala.Interface iface) { 188 | add_if_matches (iface); 189 | iface.accept_children (this); 190 | } 191 | 192 | public override void visit_lambda_expression (Vala.LambdaExpression expr) { 193 | expr.accept_children (this); 194 | } 195 | 196 | public override void visit_lock_statement (Vala.LockStatement stmt) { 197 | stmt.accept_children (this); 198 | } 199 | 200 | public override void visit_local_variable (Vala.LocalVariable local) { 201 | local.accept_children (this); 202 | } 203 | #if VALA_0_52 204 | public override void visit_loop_statement (Vala.LoopStatement stmt) { 205 | #else 206 | public override void visit_loop (Vala.Loop stmt) { 207 | #endif 208 | stmt.accept_children (this); 209 | } 210 | 211 | public override void visit_member_access (Vala.MemberAccess expr) { 212 | expr.accept_children (this); 213 | } 214 | 215 | public override void visit_method (Vala.Method m) { 216 | add_if_matches (m); 217 | m.accept_children (this); 218 | } 219 | 220 | public override void visit_method_call (Vala.MethodCall mc) { 221 | mc.accept_children (this); 222 | } 223 | 224 | public override void visit_namespace (Vala.Namespace ns) { 225 | add_if_matches (ns); 226 | ns.accept_children (this); 227 | } 228 | 229 | public override void visit_object_creation_expression (Vala.ObjectCreationExpression expr) { 230 | expr.accept_children (this); 231 | } 232 | 233 | public override void visit_postfix_expression (Vala.PostfixExpression expr) { 234 | expr.accept_children (this); 235 | } 236 | 237 | public override void visit_property (Vala.Property prop) { 238 | add_if_matches (prop); 239 | prop.accept_children (this); 240 | } 241 | 242 | public override void visit_property_accessor (Vala.PropertyAccessor acc) { 243 | add_if_matches (acc); 244 | acc.accept_children (this); 245 | } 246 | 247 | public override void visit_return_statement (Vala.ReturnStatement stmt) { 248 | stmt.accept_children (this); 249 | } 250 | 251 | public override void visit_signal (Vala.Signal sig) { 252 | sig.accept_children (this); 253 | } 254 | 255 | public override void visit_struct (Vala.Struct st) { 256 | add_if_matches (st); 257 | st.accept_children (this); 258 | } 259 | 260 | public override void visit_switch_label (Vala.SwitchLabel label) { 261 | label.accept_children (this); 262 | } 263 | 264 | public override void visit_switch_section (Vala.SwitchSection section) { 265 | add_if_matches (section); 266 | section.accept_children (this); 267 | } 268 | 269 | public override void visit_switch_statement (Vala.SwitchStatement stmt) { 270 | stmt.accept_children (this); 271 | } 272 | 273 | public override void visit_try_statement (Vala.TryStatement stmt) { 274 | stmt.accept_children (this); 275 | } 276 | 277 | public override void visit_unary_expression (Vala.UnaryExpression expr) { 278 | expr.accept_children (this); 279 | } 280 | 281 | public override void visit_while_statement (Vala.WhileStatement stmt) { 282 | stmt.accept_children (this); 283 | } 284 | 285 | #if VALA_0_50 286 | public override void visit_with_statement (Vala.WithStatement stmt) { 287 | stmt.accept_children (this); 288 | } 289 | #endif 290 | 291 | public override void visit_yield_statement (Vala.YieldStatement stmt) { 292 | stmt.accept_children (this); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /vapi/jsonrpc-glib-1.0.vapi: -------------------------------------------------------------------------------- 1 | /* jsonrpc-glib-1.0.vapi generated by vapigen, do not modify. */ 2 | 3 | [CCode (cprefix = "Jsonrpc", gir_namespace = "Jsonrpc", gir_version = "1.0", lower_case_cprefix = "jsonrpc_")] 4 | namespace Jsonrpc { 5 | namespace Message { 6 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 7 | [GIR (name = "MessageGetBoolean")] 8 | public struct GetBoolean { 9 | public Jsonrpc.MessageMagic magic; 10 | public bool valptr; 11 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_BOOLEAN")] 12 | public static void* create (ref bool val); 13 | } 14 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 15 | [GIR (name = "MessageGetDict")] 16 | public struct GetDict { 17 | public Jsonrpc.MessageMagic magic; 18 | public weak GLib.VariantDict dictptr; 19 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_DICT")] 20 | public static void* create (ref GLib.VariantDict val); 21 | } 22 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 23 | [GIR (name = "MessageGetDouble")] 24 | public struct GetDouble { 25 | public Jsonrpc.MessageMagic magic; 26 | public double valptr; 27 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_DOUBLE")] 28 | public static void* create (ref double val); 29 | } 30 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 31 | [GIR (name = "MessageGetInt32")] 32 | public struct GetInt32 { 33 | public Jsonrpc.MessageMagic magic; 34 | public int32 valptr; 35 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_INT32")] 36 | public static void* create (ref int32 val); 37 | } 38 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 39 | [GIR (name = "MessageGetInt64")] 40 | public struct GetInt64 { 41 | public Jsonrpc.MessageMagic magic; 42 | public int64 valptr; 43 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_INT64")] 44 | public static void* create (ref int64 val); 45 | } 46 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 47 | [GIR (name = "MessageGetIter")] 48 | public struct GetIter { 49 | public Jsonrpc.MessageMagic magic; 50 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_ITER")] 51 | public static void* create (ref GLib.VariantIter val); 52 | } 53 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 54 | [GIR (name = "MessageGetString")] 55 | public struct GetString { 56 | public Jsonrpc.MessageMagic magic; 57 | public weak string valptr; 58 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_STRING")] 59 | public static void* create (ref unowned string val); 60 | } 61 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 62 | [GIR (name = "MessageGetStrv")] 63 | public struct GetStrv { 64 | public Jsonrpc.MessageMagic magic; 65 | [CCode (array_length = false)] 66 | public weak string[] valptr; 67 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_STRV")] 68 | public static void* create ([CCode (array_length = false)] ref string[] val); 69 | } 70 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 71 | [GIR (name = "MessageGetVariant")] 72 | public struct GetVariant { 73 | public Jsonrpc.MessageMagic magic; 74 | public weak GLib.Variant variantptr; 75 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_GET_VARIANT")] 76 | public static void* create (ref GLib.Variant val); 77 | } 78 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 79 | [GIR (name = "MessagePutBoolean")] 80 | public struct PutBoolean { 81 | public Jsonrpc.MessageMagic magic; 82 | public bool val; 83 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_BOOLEAN")] 84 | public static void* create (bool val); 85 | } 86 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 87 | [GIR (name = "MessagePutDouble")] 88 | public struct PutDouble { 89 | public Jsonrpc.MessageMagic magic; 90 | public double val; 91 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_DOUBLE")] 92 | public static void* create (double val); 93 | } 94 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 95 | [GIR (name = "MessagePutInt32")] 96 | public struct PutInt32 { 97 | public Jsonrpc.MessageMagic magic; 98 | public int32 val; 99 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_INT32")] 100 | public static void* create (int32 val); 101 | } 102 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 103 | [GIR (name = "MessagePutInt64")] 104 | public struct PutInt64 { 105 | public Jsonrpc.MessageMagic magic; 106 | public int64 val; 107 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_INT64")] 108 | public static void* create (int64 val); 109 | } 110 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 111 | [GIR (name = "MessagePutString")] 112 | public struct PutString { 113 | public Jsonrpc.MessageMagic magic; 114 | public weak string val; 115 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_STRING")] 116 | public static void* create (string val); 117 | } 118 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 119 | [GIR (name = "MessagePutStrv")] 120 | public struct PutStrv { 121 | public Jsonrpc.MessageMagic magic; 122 | [CCode (array_length = false)] 123 | public weak string[] val; 124 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MESSAGE_PUT_STRV")] 125 | public static void* create ([CCode (array_length = false)] string[] val); 126 | } 127 | [CCode (cheader_filename = "jsonrpc-glib.h")] 128 | public static GLib.Variant @new (...); 129 | [CCode (cheader_filename = "jsonrpc-glib.h")] 130 | public static GLib.Variant new_array (...); 131 | [CCode (cheader_filename = "jsonrpc-glib.h")] 132 | public static bool parse (GLib.Variant message, ...); 133 | [CCode (cheader_filename = "jsonrpc-glib.h")] 134 | public static bool parse_array (GLib.VariantIter iter, ...); 135 | } 136 | [CCode (cheader_filename = "jsonrpc-glib.h", type_id = "jsonrpc_client_get_type ()")] 137 | [Version (since = "3.26")] 138 | public class Client : GLib.Object { 139 | [CCode (has_construct_function = false)] 140 | public Client (GLib.IOStream io_stream); 141 | public bool call (string method, GLib.Variant? @params, GLib.Cancellable? cancellable, out GLib.Variant? return_value) throws GLib.Error; 142 | public async bool call_async (string method, GLib.Variant? @params, GLib.Cancellable? cancellable, out GLib.Variant? return_value) throws GLib.Error; 143 | [CCode (finish_name = "jsonrpc_client_call_finish")] 144 | [Version (since = "3.30")] 145 | public async bool call_with_id_async (string method, GLib.Variant? @params, GLib.Variant* id, GLib.Cancellable? cancellable, out GLib.Variant? return_value) throws GLib.Error; 146 | public bool close (GLib.Cancellable? cancellable = null) throws GLib.Error; 147 | public async bool close_async (GLib.Cancellable? cancellable) throws GLib.Error; 148 | public static GLib.Quark error_quark (); 149 | public bool get_use_gvariant (); 150 | public bool reply (GLib.Variant id, GLib.Variant? result, GLib.Cancellable? cancellable = null) throws GLib.Error; 151 | public async bool reply_async (GLib.Variant id, GLib.Variant result, GLib.Cancellable? cancellable) throws GLib.Error; 152 | [Version (since = "3.28")] 153 | public async bool reply_error_async (GLib.Variant id, int code, string? message, GLib.Cancellable? cancellable) throws GLib.Error; 154 | public bool send_notification (string method, GLib.Variant? @params, GLib.Cancellable? cancellable = null) throws GLib.Error; 155 | public async bool send_notification_async (string method, GLib.Variant? @params, GLib.Cancellable? cancellable) throws GLib.Error; 156 | public void set_use_gvariant (bool use_gvariant); 157 | public void start_listening (); 158 | [NoAccessorMethod] 159 | public GLib.IOStream io_stream { construct; } 160 | public bool use_gvariant { get; set; } 161 | [Version (since = "3.28")] 162 | public virtual signal void failed (); 163 | public virtual signal bool handle_call (string method, GLib.Variant id, GLib.Variant? @params); 164 | public virtual signal void notification (string method_name, GLib.Variant? @params); 165 | } 166 | [CCode (cheader_filename = "jsonrpc-glib.h", type_id = "jsonrpc_input_stream_get_type ()")] 167 | public class InputStream : GLib.DataInputStream, GLib.Seekable { 168 | [CCode (has_construct_function = false)] 169 | public InputStream (GLib.InputStream base_stream); 170 | public bool read_message (GLib.Cancellable? cancellable, GLib.Variant message) throws GLib.Error; 171 | public async bool read_message_async (GLib.Cancellable? cancellable) throws GLib.Error; 172 | } 173 | [CCode (cheader_filename = "jsonrpc-glib.h", type_id = "jsonrpc_output_stream_get_type ()")] 174 | public class OutputStream : GLib.DataOutputStream, GLib.Seekable { 175 | [CCode (has_construct_function = false)] 176 | public OutputStream (GLib.OutputStream base_stream); 177 | public bool get_use_gvariant (); 178 | public void set_use_gvariant (bool use_gvariant); 179 | [Version (since = "3.26")] 180 | public bool write_message (GLib.Variant message, GLib.Cancellable? cancellable = null) throws GLib.Error; 181 | [Version (since = "3.26")] 182 | public async bool write_message_async (GLib.Variant message, GLib.Cancellable? cancellable) throws GLib.Error; 183 | public bool use_gvariant { get; set; } 184 | } 185 | [CCode (cheader_filename = "jsonrpc-glib.h", type_id = "jsonrpc_server_get_type ()")] 186 | public class Server : GLib.Object { 187 | [CCode (has_construct_function = false)] 188 | [Version (since = "3.26")] 189 | public Server (); 190 | [Version (since = "3.26")] 191 | public void accept_io_stream (GLib.IOStream io_stream); 192 | [Version (since = "3.26")] 193 | public uint add_handler (string method, owned Jsonrpc.ServerHandler handler); 194 | [Version (since = "3.28")] 195 | public void @foreach (GLib.Func foreach_func); 196 | [Version (since = "3.26")] 197 | public void remove_handler (uint handler_id); 198 | [Version (since = "3.28")] 199 | public virtual signal void client_accepted (Jsonrpc.Client client); 200 | [Version (since = "3.30")] 201 | public virtual signal void client_closed (Jsonrpc.Client client); 202 | [Version (since = "3.26")] 203 | public virtual signal bool handle_call (Jsonrpc.Client client, string method, GLib.Variant id, GLib.Variant @params); 204 | [Version (since = "3.26")] 205 | public virtual signal void notification (Jsonrpc.Client client, string method, GLib.Variant @params); 206 | } 207 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 208 | public struct MessageAny { 209 | public Jsonrpc.MessageMagic magic; 210 | } 211 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 212 | public struct MessageMagic { 213 | [CCode (array_length = false)] 214 | public weak char bytes[8]; 215 | } 216 | [CCode (cheader_filename = "jsonrpc-glib.h", has_type_id = false)] 217 | public struct MessagePutVariant { 218 | public Jsonrpc.MessageMagic magic; 219 | public weak GLib.Variant val; 220 | } 221 | [CCode (cheader_filename = "jsonrpc-glib.h", cprefix = "JSONRPC_CLIENT_ERROR_", has_type_id = false)] 222 | public enum ClientError { 223 | PARSE_ERROR, 224 | INVALID_REQUEST, 225 | METHOD_NOT_FOUND, 226 | INVALID_PARAMS, 227 | INTERNAL_ERROR 228 | } 229 | [CCode (cheader_filename = "jsonrpc-glib.h", instance_pos = 5.9)] 230 | public delegate void ServerHandler (Jsonrpc.Server self, Jsonrpc.Client client, string method, GLib.Variant id, GLib.Variant @params); 231 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MAJOR_VERSION")] 232 | public const int MAJOR_VERSION; 233 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MICRO_VERSION")] 234 | public const int MICRO_VERSION; 235 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_MINOR_VERSION")] 236 | public const int MINOR_VERSION; 237 | [CCode (cheader_filename = "jsonrpc-glib.h", cname = "JSONRPC_VERSION_S")] 238 | public const string VERSION_S; 239 | } 240 | -------------------------------------------------------------------------------- /src/documentation/doccomment.vala: -------------------------------------------------------------------------------- 1 | /* doccomment.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | /** 22 | * Represents a documentation comment after it has been 23 | * formatted into Markdown. 24 | */ 25 | class Vls.DocComment { 26 | /** 27 | * The main body of the comment, in Markdown. 28 | * {@inheritDoc} 29 | */ 30 | public string body { get; set; default = ""; } 31 | 32 | /** 33 | * The list of parameters for this symbol. The key is the 34 | * parameter name and the value is the Markdown documentation for 35 | * that parameter. 36 | */ 37 | public HashMap parameters { get; private set; default = new HashMap (); } 38 | 39 | /** 40 | * The comment about the return value, if applicable. 41 | */ 42 | public string? return_body { get; private set; } 43 | 44 | /** 45 | * Create a documentation comment from a Markdown string. No rendering is done. 46 | */ 47 | public DocComment (string markdown_doc) { 48 | body = markdown_doc; 49 | } 50 | 51 | /** 52 | * Render a GTK-Doc- or gi-docgen-formatted comment into Markdown. 53 | * 54 | * see [[https://developer.gnome.org/gtk-doc-manual/stable/documenting_syntax.html.en]] 55 | * and [[https://gnome.pages.gitlab.gnome.org/gi-docgen/linking.html]] 56 | * 57 | * @param comment A comment in GTK-Doc or gi-docgen format 58 | * @param documentation Holds GIR documentation and renders the comment 59 | * @param compilation The current compilation that is the context of the comment 60 | */ 61 | public DocComment.from_gir_comment (Vala.Comment comment, GirDocumentation documentation, Compilation compilation) throws RegexError { 62 | Regex regex = /\[(\w+)@(\w+(\.\w+)*)(::?([\w\-]+))?\]/; 63 | body = regex.match (comment.content) ? 64 | documentation.render_gi_docgen_comment (comment, compilation) : 65 | documentation.render_gtk_doc_comment (comment, compilation); 66 | if (comment is Vala.GirComment) { 67 | var gir_comment = (Vala.GirComment) comment; 68 | for (var it = gir_comment.parameter_iterator (); it.next (); ) { 69 | Vala.Comment param_comment = it.get_value (); 70 | parameters[it.get_key ()] = regex.match (param_comment.content) ? 71 | documentation.render_gi_docgen_comment (param_comment, compilation) : 72 | documentation.render_gtk_doc_comment (param_comment, compilation); 73 | } 74 | if (gir_comment.return_content != null) 75 | return_body = regex.match (gir_comment.return_content.content) ? 76 | documentation.render_gi_docgen_comment (gir_comment.return_content, compilation) : 77 | documentation.render_gtk_doc_comment (gir_comment.return_content, compilation); 78 | } 79 | } 80 | 81 | /** 82 | * Render a ValaDoc-formatted comment into Markdown. 83 | * 84 | * see [[https://valadoc.org/markup.htm]] 85 | * 86 | * @param comment a comment in ValaDoc format 87 | * @param symbol the symbol associated with the comment 88 | * @param compilation the current compilation that is the context of the comment 89 | */ 90 | public DocComment.from_valadoc_comment (Vala.Comment comment, Vala.Symbol symbol, Compilation compilation) throws RegexError { 91 | body = comment.content; 92 | 93 | // strip the comment of asterisks 94 | body = /^[\t\f\v ]*\*+[ \t\f]*(.*)$/m.replace (body, body.length, 0, "\\1"); 95 | 96 | // render highlighting hints: bold, underlined, italic, and block quote 97 | // bold 98 | body = /''(.*?)''/.replace (body, body.length, 0, "**\\1**"); 99 | // underlined 100 | body = /__(.*)__/.replace (body, body.length, 0, "\\1"); 101 | // italic 102 | body = /\/\/(.*?)\/\//.replace (body, body.length, 0, "_\\1_"); 103 | // quotes 104 | body = /\n?(? { 105 | string quote = match_info.fetch (1) ?? ""; 106 | 107 | if (quote.index_of_char ('\n') == -1) { 108 | // inline quotes 109 | result.append_c ('`'); 110 | result.append (quote); 111 | result.append_c ('`'); 112 | } else { 113 | // block quotes 114 | result.append_c ('\n'); 115 | foreach (string line in quote.split ("\n")) { 116 | result.append ("> "); 117 | result.append (line); 118 | result.append_c ('\n'); 119 | } 120 | result.append_c ('\n'); 121 | } 122 | return false; 123 | }); 124 | 125 | // TODO: we'll avoid rendering all of the kinds of lists now, since some are already 126 | // supported by markdown 127 | 128 | // code blocks (with support for a non-standard language specifier) 129 | body = /{{{(\w+)?(.*?)}}}/s.replace (body, body.length, 0, "```\\1\\2```"); 130 | 131 | // images and links 132 | body = /(\[\[|{{)([~:\/\\\w\-.]+)(\|(.*?))?(\]\]|}})/ 133 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 134 | string type = match_info.fetch (1) ?? ""; 135 | string href = match_info.fetch (2) ?? ""; 136 | string name = match_info.fetch (4) ?? ""; 137 | string end = match_info.fetch (5) ?? ""; 138 | 139 | if (!(type == "[[" && end == "]]" || type == "{{" && end == "}}")) { 140 | result.append ((!) match_info.fetch (0)); 141 | } else { // image or link 142 | if (name == "" && type == "[[") 143 | result.append (href); 144 | else { 145 | if (type == "{{") 146 | result.append_c ('!'); 147 | result.append_c ('['); 148 | result.append (name); 149 | result.append ("]("); 150 | result.append (href); 151 | result.append_c (')'); 152 | } 153 | } 154 | return false; 155 | }); 156 | 157 | // tables 158 | body = /(?'header'\|\|(.*?\|\|)+(\n|$))(?'rest'(?&header)+)/ 159 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 160 | string header = match_info.fetch_named ("header") ?? ""; 161 | string rest = match_info.fetch_named ("rest") ?? ""; 162 | var columns_regex = /\|\|((([^`\n\r]|`.*?`)*?)(?=\|\|))?/; 163 | MatchInfo header_minfo; 164 | 165 | if (!columns_regex.match (header, 0, out header_minfo)) { 166 | result.append ("\n\n(failed to render ValaDoc table)\n\n"); 167 | return false; 168 | } 169 | 170 | try { 171 | result.append (columns_regex.replace (header, header.length, 0, "|\\1")); 172 | result.append_c ('|'); 173 | string header_content = ""; 174 | while (header_minfo.matches () && (header_content = (header_minfo.fetch (1) ?? "")).strip () != "") { 175 | result.append (string.nfill (header_content.length, '-')); 176 | result.append_c ('|'); 177 | header_minfo.next (); 178 | } 179 | result.append_c ('\n'); 180 | result.append (columns_regex.replace (rest, rest.length, 0, "|\\1")); 181 | } catch (RegexError e) { 182 | warning ("failed to render ValaDoc table - %s", e.message); 183 | result.append ("\n\n(failed to render ValaDoc table)\n\n"); 184 | } 185 | 186 | return false; 187 | }); 188 | 189 | // render headlines 190 | body = /^(?=+) (.+?) (?P=prefix)$/m 191 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 192 | string prefix = (!) match_info.fetch_named ("prefix"); 193 | string heading = (!) match_info.fetch (2); 194 | 195 | result.append (string.nfill (prefix.length, '#')); 196 | result.append_c (' '); 197 | result.append (heading); 198 | return false; 199 | }); 200 | 201 | // inline taglets 202 | DocComment? parent_comment = null; 203 | bool computed_parent = false; 204 | body = /{@inheritDoc}/.replace_eval (body, body.length, 0, 0, (match_info, result) => { 205 | if (!computed_parent) { 206 | computed_parent = true; 207 | if (symbol.parent_symbol != null && symbol.parent_symbol.comment != null) { 208 | try { 209 | parent_comment = new DocComment.from_valadoc_comment (symbol.parent_symbol.comment, symbol.parent_symbol, compilation); 210 | } catch (RegexError e) { 211 | warning ("could not render comment - could not render parent comment - %s", e.message); 212 | result.append ("(could not render parent comment - "); 213 | result.append (e.message); 214 | result.append_c (')'); 215 | return true; 216 | } 217 | } 218 | if (parent_comment != null) 219 | result.append (parent_comment.body); 220 | } 221 | return false; 222 | }); 223 | 224 | // inline references to other symbols (XXX: should we tranform these into links?) 225 | body = /{@link ((?'ident'[A-Za-z_]\w*)(\.(?&ident))*?)}/.replace (body, body.length, 0, "**\\1**"); 226 | 227 | // block taglets: @param 228 | body = /^@param ([A-Za-z_]\w*)[\t\f\v ]+(.+(\n[\t\f ]?([^@]|@(?!deprecated|see|param|since|return|throws))+)*)$/m 229 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 230 | string param_name = (!) match_info.fetch (1); 231 | string param_description = (!) match_info.fetch (2); 232 | 233 | parameters[param_name] = param_description; 234 | return false; 235 | }); 236 | 237 | // block taglets: @return 238 | body = /^@return[\t\f\v ]+(.+(\n[\t\f ]?([^@]|@(?!deprecated|see|param|since|return|throws))+)*)$/m 239 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 240 | return_body = (!) match_info.fetch (1); 241 | return false; 242 | }); 243 | 244 | // block taglets: @see 245 | body = /^@see[\t\f ]+((?'ident'[A-Za-z_]\w*)(\.(?&ident))*?)$/m 246 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 247 | string symbol_name = match_info.fetch (1); 248 | result.append ("\n\n**see** "); 249 | result.append ("`"); 250 | result.append (symbol_name); 251 | result.append ("`"); 252 | return false; 253 | }); 254 | 255 | // block taglets: @since 256 | body = /^@since[\t\f ]+((?'ident'\S+)(\.(?&ident))*?)$/m 257 | .replace_eval (body, body.length, 0, 0, (match_info, result) => { 258 | string version = match_info.fetch (1); 259 | result.append ("\n---\n\n**Since** "); 260 | result.append (version); 261 | return false; 262 | }); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/codehelp/formatter.vala: -------------------------------------------------------------------------------- 1 | /* formatter.vala 2 | * 3 | * Copyright 2022 JCWasmx86 4 | * 5 | * This file is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation; either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This file is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: LGPL-2.1-or-later 19 | */ 20 | 21 | using Gee; 22 | using Lsp; 23 | 24 | errordomain FormattingError { 25 | FORMAT, 26 | READ 27 | } 28 | 29 | namespace Vls.Formatter { 30 | TextEdit format (FormattingOptions options, 31 | CodeStyleAnalyzer? analyzed_style, 32 | Vala.SourceFile source, Range? range = null, 33 | Cancellable? cancellable = null) throws FormattingError, Error { 34 | // SEARCH_PATH_FROM_ENVP does not seem to be available even in quite fast distros like Fedora 35, 35 | // so we have to use a SubprocessLauncher and call set_environ() 36 | var launcher = new SubprocessLauncher (SubprocessFlags.STDERR_PIPE | SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDIN_PIPE); 37 | launcher.set_environ (Environ.get ()); 38 | var args = get_uncrustify_args (source, options, analyzed_style, cancellable); 39 | Subprocess subprocess = launcher.spawnv (args); 40 | string stdin_buf; 41 | if (range == null) { 42 | stdin_buf = source.content; 43 | } else { 44 | var from = (long)Util.get_string_pos (source.content, range.start.line, range.start.character); 45 | var to = (long)Util.get_string_pos (source.content, range.end.line, range.end.character); 46 | stdin_buf = source.content[from:to]; 47 | } 48 | string? stdout_buf = null, stderr_buf = null; 49 | subprocess.communicate_utf8 (stdin_buf, cancellable, out stdout_buf, out stderr_buf); 50 | if (!subprocess.get_successful ()) { 51 | if (stderr_buf != null && stderr_buf.strip ().length > 0) { 52 | throw new FormattingError.FORMAT ("%s", stderr_buf); 53 | } else { 54 | var sb = new StringBuilder (); 55 | foreach (var arg in args) 56 | sb.append (arg).append (" "); 57 | throw new FormattingError.READ ("uncrustify failed with error code %d: %s", subprocess.get_exit_status (), sb.str.strip ()); 58 | } 59 | } 60 | Range edit_range; 61 | if (range != null) { 62 | edit_range = range; 63 | } else { 64 | int last_nl_pos; 65 | uint nl_count = Util.count_chars_in_string (stdin_buf, '\n', out last_nl_pos); 66 | edit_range = new Range () { 67 | start = new Position () { 68 | line = 0, 69 | character = 0 70 | }, 71 | end = new Position () { 72 | line = nl_count + 1, 73 | // handle trailing newline 74 | character = last_nl_pos == stdin_buf.length - 1 ? 1 : 0 75 | } 76 | }; 77 | } 78 | return new TextEdit (edit_range, stdout_buf); 79 | } 80 | 81 | string[] get_uncrustify_args (Vala.SourceFile source, FormattingOptions options, CodeStyleAnalyzer? analyzed_style, Cancellable? cancellable = null) { 82 | // Check if the project has a local uncrustify config file and use that instead 83 | var cwd = File.new_for_path (Environment.get_current_dir ()); 84 | string[] config_paths = { ".uncrustify.cfg", "uncrustify.cfg" }; 85 | foreach (string filename in config_paths) { 86 | var child = cwd.get_child (filename); 87 | if (child.query_exists (cancellable)) { 88 | return { "uncrustify", "-c", child.get_path (), "--assume", source.filename, "-q" }; 89 | } 90 | } 91 | 92 | // No config file found... Use defaults 93 | var conf = new HashMap (); 94 | // https://github.com/uncrustify/uncrustify/blob/master/documentation/htdocs/default.cfg 95 | conf["indent_with_tabs"] = "%d".printf (options.insertSpaces ? 0 : 1); 96 | conf["nl_end_of_file"] = options.insertFinalNewline ? "force" : "remove"; 97 | conf["nl_end_of_file_min"] = "%d".printf (options.trimFinalNewlines ? 1 : 0); 98 | conf["output_tab_size"] = "%u".printf (options.tabSize); 99 | conf["pos_arith"] = "lead"; 100 | conf["indent_paren_nl"] = "true"; 101 | conf["indent_comma_brace"] = "1"; 102 | conf["indent_columns"] = "%u".printf (options.tabSize); 103 | conf["indent_align_string"] = "true"; 104 | conf["indent_xml_string"] = "%u".printf (options.tabSize); 105 | conf["indent_namespace"] = "true"; 106 | conf["indent_class"] = "true"; 107 | conf["indent_var_def_cont"] = "true"; 108 | conf["indent_func_def_param"] = "false"; 109 | conf["indent_func_proto_param"] = "true"; 110 | conf["indent_func_class_param"] = "true"; 111 | conf["indent_func_ctor_var_param"] = "true"; 112 | conf["indent_template_param"] = "true"; 113 | conf["indent_member"] = "1"; 114 | conf["indent_paren_close"] = "2"; 115 | conf["indent_align_assign"] = "false"; 116 | conf["indent_oc_block_msg_xcode_style"] = "true"; 117 | conf["indent_oc_block_msg_from_keyword"] = "true"; 118 | conf["indent_oc_block_msg_from_colon"] = "true"; 119 | conf["indent_oc_block_msg_from_caret"] = "true"; 120 | conf["indent_oc_block_msg_from_brace"] = "true"; 121 | conf["newlines"] = "auto"; 122 | conf["sp_arith"] = "force"; 123 | conf["sp_assign"] = "force"; 124 | conf["sp_assign_default"] = "force"; 125 | conf["sp_before_assign"] = "force"; 126 | conf["sp_after_assign"] = "force"; 127 | conf["sp_enum_assign"] = "force"; 128 | conf["sp_enum_after_assign"] = "force"; 129 | conf["sp_bool"] = "force"; 130 | conf["sp_compare"] = "force"; 131 | conf["sp_inside_paren"] = "remove"; 132 | conf["sp_paren_paren"] = "remove"; 133 | conf["sp_cparen_oparen"] = "force"; 134 | conf["sp_paren_brace"] = "force"; 135 | conf["sp_before_ptr_star"] = "remove"; 136 | conf["sp_before_unnamed_ptr_star"] = "remove"; 137 | conf["sp_between_ptr_star"] = "force"; 138 | conf["sp_after_ptr_star"] = "force"; 139 | conf["sp_after_ptr_star_func"] = "force"; 140 | conf["sp_ptr_star_paren"] = "force"; 141 | conf["sp_before_ptr_star_func"] = "force"; 142 | conf["sp_before_byref"] = "force"; 143 | conf["sp_after_byref_func"] = "remove"; 144 | conf["sp_before_byref_func"] = "force"; 145 | conf["sp_before_angle"] = "remove"; 146 | conf["sp_inside_angle"] = "remove"; 147 | conf["sp_after_angle"] = "remove"; 148 | conf["sp_angle_paren"] = "force"; 149 | conf["sp_angle_paren_empty"] = "force"; 150 | conf["sp_angle_word"] = "force"; 151 | conf["sp_angle_shift"] = "remove"; 152 | conf["sp_permit_cpp11_shift"] = "true"; 153 | conf["sp_before_sparen"] = "force"; 154 | conf["sp_inside_sparen"] = "remove"; 155 | conf["sp_after_sparen"] = "remove"; 156 | conf["sp_sparen_brace"] = "force"; 157 | conf["sp_special_semi"] = "remove"; 158 | conf["sp_before_semi_for"] = "remove"; 159 | conf["sp_before_semi_for_empty"] = "force"; 160 | conf["sp_after_semi_for_empty"] = "force"; 161 | conf["sp_before_square"] = "remove"; 162 | conf["sp_before_squares"] = "remove"; 163 | conf["sp_inside_square"] = "remove"; 164 | conf["sp_after_comma"] = "force"; 165 | conf["sp_before_ellipsis"] = "remove"; 166 | conf["sp_after_class_colon"] = "force"; 167 | conf["sp_before_class_colon"] = "force"; 168 | conf["sp_after_constr_colon"] = "ignore"; 169 | conf["sp_before_constr_colon"] = "ignore"; 170 | conf["sp_after_operator"] = "force"; 171 | conf["sp_after_cast"] = "force"; 172 | conf["sp_inside_paren_cast"] = "remove"; 173 | conf["sp_sizeof_paren"] = "force"; 174 | conf["sp_inside_braces_enum"] = "force"; 175 | conf["sp_inside_braces_struct"] = "force"; 176 | conf["sp_inside_braces"] = "force"; 177 | conf["sp_inside_braces_empty"] = "remove"; 178 | conf["sp_type_func"] = "remove"; 179 | conf["sp_inside_fparens"] = "remove"; 180 | conf["sp_inside_fparen"] = "remove"; 181 | conf["sp_inside_tparen"] = "remove"; 182 | conf["sp_after_tparen_close"] = "remove"; 183 | conf["sp_square_fparen"] = "force"; 184 | conf["sp_fparen_brace"] = "force"; 185 | if (analyzed_style != null && analyzed_style.average_spacing_before_parens > 0) { 186 | conf["sp_func_proto_paren"] = "force"; 187 | conf["sp_func_def_paren"] = "force"; 188 | conf["sp_func_class_paren"] = "force"; 189 | conf["sp_func_call_paren"] = "force"; 190 | } else { 191 | conf["sp_func_proto_paren"] = "remove"; 192 | conf["sp_func_def_paren"] = "remove"; 193 | conf["sp_func_class_paren"] = "remove"; 194 | conf["sp_func_call_paren"] = "remove"; 195 | } 196 | conf["sp_vala_after_translation"] = "remove"; 197 | conf["sp_func_call_paren_empty"] = "ignore"; 198 | conf["sp_return_paren"] = "force"; 199 | conf["sp_attribute_paren"] = "force"; 200 | conf["sp_defined_paren"] = "force"; 201 | conf["sp_throw_paren"] = "force"; 202 | conf["sp_after_throw"] = "force"; 203 | conf["sp_catch_paren"] = "force"; 204 | conf["sp_else_brace"] = "force"; 205 | conf["sp_brace_else"] = "force"; 206 | conf["sp_brace_typedef"] = "force"; 207 | conf["sp_catch_brace"] = "force"; 208 | conf["sp_brace_catch"] = "force"; 209 | conf["sp_finally_brace"] = "force"; 210 | conf["sp_brace_finally"] = "force"; 211 | conf["sp_try_brace"] = "force"; 212 | conf["sp_getset_brace"] = "force"; 213 | conf["sp_word_brace_ns"] = "force"; 214 | conf["sp_before_dc"] = "remove"; 215 | conf["sp_after_dc"] = "remove"; 216 | conf["sp_cond_colon"] = "force"; 217 | conf["sp_cond_colon_before"] = "force"; 218 | conf["sp_cond_question"] = "force"; 219 | conf["sp_cond_question_after"] = "force"; 220 | conf["sp_cond_ternary_short"] = "force"; 221 | conf["sp_case_label"] = "force"; 222 | conf["sp_cmt_cpp_start"] = "force"; 223 | conf["sp_endif_cmt"] = "remove"; 224 | conf["sp_after_new"] = "force"; 225 | conf["sp_before_tr_cmt"] = "force"; 226 | conf["align_keep_extra_space"] = "true"; 227 | conf["nl_assign_leave_one_liners"] = "true"; 228 | conf["nl_class_leave_one_liners"] = "true"; 229 | conf["nl_enum_brace"] = "remove"; 230 | conf["nl_struct_brace"] = "remove"; 231 | conf["nl_union_brace"] = "remove"; 232 | conf["nl_if_brace"] = "remove"; 233 | conf["nl_brace_else"] = "remove"; 234 | conf["nl_elseif_brace"] = "remove"; 235 | conf["nl_else_brace"] = "remove"; 236 | conf["nl_else_if"] = "remove"; 237 | conf["nl_brace_finally"] = "remove"; 238 | conf["nl_finally_brace"] = "remove"; 239 | conf["nl_try_brace"] = "remove"; 240 | conf["nl_getset_brace"] = "remove"; 241 | conf["nl_for_brace"] = "remove"; 242 | conf["nl_catch_brace"] = "remove"; 243 | conf["nl_brace_catch"] = "remove"; 244 | conf["nl_brace_square"] = "remove"; 245 | conf["nl_brace_fparen"] = "remove"; 246 | conf["nl_while_brace"] = "remove"; 247 | conf["nl_using_brace"] = "remove"; 248 | conf["nl_do_brace"] = "remove"; 249 | conf["nl_brace_while"] = "remove"; 250 | conf["nl_switch_brace"] = "remove"; 251 | conf["nl_before_throw"] = "remove"; 252 | conf["nl_namespace_brace"] = "remove"; 253 | conf["nl_class_brace"] = "remove"; 254 | conf["nl_class_init_args"] = "remove"; 255 | conf["nl_class_init_args"] = "remove"; 256 | conf["nl_func_type_name"] = "remove"; 257 | conf["nl_func_type_name_class"] = "remove"; 258 | conf["nl_func_proto_type_name"] = "remove"; 259 | conf["nl_func_paren"] = "remove"; 260 | conf["nl_func_def_paren"] = "remove"; 261 | conf["nl_func_decl_start"] = "remove"; 262 | conf["nl_func_def_start"] = "remove"; 263 | conf["nl_func_decl_end"] = "remove"; 264 | conf["nl_func_def_end"] = "remove"; 265 | conf["nl_func_decl_empty"] = "remove"; 266 | conf["nl_func_def_empty"] = "remove"; 267 | conf["nl_fdef_brace"] = "remove"; 268 | conf["nl_return_expr"] = "remove"; 269 | conf["nl_after_func_proto_group"] = "2"; 270 | conf["nl_after_func_body"] = "2"; 271 | conf["nl_after_func_body_class"] = "2"; 272 | conf["eat_blanks_before_close_brace"] = "true"; 273 | conf["pp_indent_count"] = "0"; 274 | string[] args = { "uncrustify", "-c", "-", "--assume", source.filename, "-q" }; 275 | foreach (var entry in conf.entries) { 276 | args += "--set"; 277 | args += @"$(entry.key)=$(entry.value)"; 278 | } 279 | return args; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/projects/buildtask.vala: -------------------------------------------------------------------------------- 1 | /* buildtask.vala 2 | * 3 | * Copyright 2020 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | /** 22 | * Represents a build target when it is an arbitrary command. 23 | */ 24 | class Vls.BuildTask : BuildTarget { 25 | private string[] arguments = {}; 26 | private string exe_name = ""; 27 | private string build_dir; 28 | private SubprocessLauncher launcher; 29 | 30 | /** 31 | * Because a built task could be any command, we don't know whether the files 32 | * that appear in the arg list are inputs or outputs initially. 33 | */ 34 | public ArrayList used_files { get; private set; default = new ArrayList (); } 35 | 36 | /** 37 | * Candidate inputs are files that _may_ be inputs (and only inputs) to 38 | * this task but that we cannot determine yet. They may not exist now, but 39 | * they are expected to exist at the time this target is built. 40 | */ 41 | public ArrayList candidate_inputs { get; private set; default = new ArrayList (); } 42 | 43 | private FileCache _file_cache; 44 | 45 | public BuildTask (FileCache file_cache, string build_dir, string output_dir, string name, string id, int no, 46 | string[] compiler, string[] args, string[] sources, string[] generated_sources, 47 | string[] target_output_files, 48 | string language) throws Error { 49 | base (output_dir, name, id, no); 50 | _file_cache = file_cache; 51 | // don't pipe stderr since we want to print that if something goes wrong 52 | this.build_dir = build_dir; 53 | launcher = new SubprocessLauncher (SubprocessFlags.STDOUT_PIPE); 54 | launcher.set_cwd (build_dir); 55 | 56 | foreach (string arg in compiler) { 57 | if (arguments.length > 0) 58 | exe_name += " "; 59 | exe_name += arg; 60 | arguments += arg; 61 | } 62 | 63 | foreach (string arg in args) { 64 | if (arguments.length > 0) 65 | exe_name += " "; 66 | exe_name += arg; 67 | arguments += arg; 68 | } 69 | 70 | foreach (string arg in arguments) { 71 | if (Util.arg_is_vala_file (arg)) { 72 | var file = File.new_for_commandline_arg_and_cwd (arg, output_dir); 73 | used_files.add (file); 74 | } 75 | } 76 | 77 | // if this is a C compilation, avoid colored output, which comes out badly when piped 78 | if (language == "c") { 79 | // this is a synthetic argument, so don't add it to exe_name, which is used for debug output 80 | arguments += "-fdiagnostics-color=never"; 81 | } 82 | 83 | foreach (string arg in sources) { 84 | // we don't need to add arg to arguments here since we've probably already substituted 85 | // the arguments in [compiler] and [args] from the sources, unless this is 86 | // a C code target 87 | if (language == "c") { 88 | if (arguments.length > 0) 89 | exe_name += " "; 90 | exe_name += arg; 91 | arguments += arg; 92 | } 93 | input.add (File.new_for_commandline_arg_and_cwd (arg, output_dir)); 94 | } 95 | 96 | foreach (string arg in generated_sources) { 97 | arguments += arg; 98 | input.add (File.new_for_commandline_arg_and_cwd (arg, output_dir)); 99 | } 100 | 101 | string? cmd_basename = compiler.length > 0 ? Path.get_basename (compiler[0]) : null; 102 | 103 | if (cmd_basename == "vapigen") { 104 | string? library_name = null; 105 | string? directory = null; 106 | 107 | string? flag_name, arg_value; // --[=] 108 | // it turns out we can reuse this function for vapigen 109 | for (int arg_i = -1; (arg_i = Util.iterate_valac_args (arguments, out flag_name, out arg_value, arg_i)) < arguments.length;) { 110 | if (flag_name == "directory") 111 | directory = arg_value; 112 | else if (flag_name == "library") 113 | library_name = arg_value; 114 | } 115 | 116 | if (library_name != null) { 117 | if (directory == null) { 118 | warning ("BuildTask(%s): no --directory for vapigen, assuming %s", id, output_dir); 119 | directory = output_dir; 120 | } 121 | output.add (File.new_for_commandline_arg_and_cwd (@"$library_name.vapi", directory)); 122 | } 123 | } else { 124 | // add all outputs for the target otherwise 125 | foreach (string? output_filename in target_output_files) { 126 | if (output_filename != null) { 127 | output.add (File.new_for_commandline_arg_and_cwd (output_filename, output_dir)); 128 | debug ("BuildTask(%s): outputs %s", id, output_filename); 129 | } 130 | } 131 | } 132 | 133 | if (cmd_basename == "glib-mkenums" || 134 | cmd_basename == "g-ir-scanner" || 135 | cmd_basename == "glib-compile-resources") { 136 | File output_file; 137 | // just assume the target is well-formed here 138 | if (target_output_files.length >= 1) { 139 | output_file = File.new_for_path (target_output_files[0]); 140 | // If this glib-mkenums target outputs a C file, it might be paired with a 141 | // glib-mkenums target that outputs a C header. 142 | if (cmd_basename == "glib-mkenums" && target_output_files[0].has_suffix (".c")) 143 | input.add (File.new_for_path (target_output_files[0].substring (0, target_output_files[0].length - 2) + ".h")); 144 | } else { 145 | throw new ProjectError.INTROSPECTION (@"BuildTask($id) expected at least one output file for target"); 146 | } 147 | 148 | if (cmd_basename == "g-ir-scanner") { 149 | // for g-ir-scanner, look for --library [library name] and add this to our input 150 | string? last_arg = null; 151 | string? gir_library_name = null; 152 | File? gir_library_dir = output_file.get_parent (); 153 | foreach (string arg in arguments) { 154 | if (last_arg == "--library") 155 | gir_library_name = arg; 156 | last_arg = arg; 157 | } 158 | 159 | if (gir_library_name != null) { 160 | if (gir_library_dir != null) { 161 | // XXX: how will the shared library suffix differ on other operating systems? 162 | var tried = new ArrayList (); 163 | bool success = false; 164 | File? library_file = null; 165 | foreach (string shlib_suffix in new string[]{"so", "dll"}) { 166 | library_file = gir_library_dir.get_child (@"lib$gir_library_name.$shlib_suffix"); 167 | if (library_file.query_exists ()) { 168 | input.add (library_file); 169 | debug ("BuildTask(%s) found input %s", id, library_file.get_path ()); 170 | success = true; 171 | break; 172 | } 173 | 174 | // Meson 0.55: try also looking for directory with .p suffix, 175 | // which will indicate where the library file is expected to be. 176 | File libdir = gir_library_dir.get_child (@"lib$gir_library_name.$shlib_suffix.p"); 177 | if (libdir.query_exists ()) { 178 | input.add (library_file); 179 | debug ("BuildTask(%s) found input %s", id, library_file.get_path ()); 180 | success = true; 181 | break; 182 | } 183 | 184 | tried.add (library_file); 185 | } 186 | if (!success) { 187 | warning ("BuildTask(%s) failed to determine g-ir-scanner input because all options don't exist (tried %s)", 188 | id, tried.map (f => f.get_path ()) 189 | .fold ((rightacc, elem) => elem != "" ? @"$elem, $rightacc" : rightacc, "")); 190 | } else { 191 | // The library file referenced by g-ir-scanner may be a symlink to 192 | // the actual library generated by another target. In order to make 193 | // later dependency resolution work, we need to add a dependency for the 194 | // real file. To do this, we read the location pointed to by the symlink. 195 | File real_file = library_file; 196 | try { 197 | FileInfo? info = real_file.query_info ("standard::*", FileQueryInfoFlags.NONE); 198 | // sometimes there can be symlinks to symlinks, 199 | // such as libthing.so -> libthing.so.0 -> libthing.so.0.0.0 200 | while (info.get_is_symlink ()) { 201 | real_file = gir_library_dir.get_child (info.get_symlink_target ()); 202 | try { 203 | info = real_file.query_info ("standard::*", FileQueryInfoFlags.NONE); 204 | } catch (Error e) { 205 | // if this file does not exist, then it's probably the real file, 206 | // yet-to-be-created 207 | info = null; 208 | break; 209 | } 210 | } 211 | if ((info == null || !info.get_is_symlink ()) && real_file != library_file) { 212 | input.add (real_file); 213 | debug ("BuildTask(%s): found input from symlink - %s", id, real_file.get_path ()); 214 | } 215 | } catch (Error e) { 216 | // we don't care if the file doesn't exist, since that probably means 217 | // it has yet to be generated by the target, and it's not a symlink 218 | } 219 | } 220 | } else { 221 | warning ("BuildTask(%s): could not get directory for .gir file", id); 222 | } 223 | } else { 224 | warning ("BuildTask(%s): could not get library for g-ir-scanner task", id); 225 | } 226 | } else if (cmd_basename == "glib-compile-resources") { 227 | File[] source_dirs = {}; 228 | string? last_arg = null; 229 | 230 | foreach (string arg in arguments) { 231 | if (last_arg == "--sourcedir") 232 | source_dirs += File.new_for_commandline_arg_and_cwd (arg, output_dir); 233 | last_arg = arg; 234 | } 235 | 236 | foreach (var input_file in input) { 237 | if (!input_file.get_path ().has_suffix (".gresources.xml")) 238 | break; 239 | 240 | string gresources_xml; 241 | FileUtils.get_contents (input_file.get_path (), out gresources_xml); 242 | var potential_gresources = Util.discover_gresources_xml_input_files (gresources_xml, source_dirs); 243 | candidate_inputs.add_all_array (potential_gresources); 244 | used_files.add_all_array (potential_gresources); 245 | } 246 | } 247 | } 248 | } 249 | 250 | public override void build_if_stale (Cancellable? cancellable = null) throws Error { 251 | // don't run this task if our inputs haven't changed 252 | if (!input.is_empty && !output.is_empty) { 253 | bool inputs_modified_after = false; 254 | 255 | foreach (File file in input) { 256 | FileInfo info = file.query_info (FileAttribute.TIME_MODIFIED, FileQueryInfoFlags.NONE, cancellable); 257 | DateTime? file_last_modified; 258 | #if GLIB_2_62 259 | file_last_modified = info.get_modification_date_time (); 260 | #else 261 | TimeVal time_last_modified = info.get_modification_time (); 262 | file_last_modified = new DateTime.from_iso8601 (time_last_modified.to_iso8601 (), null); 263 | #endif 264 | if (file_last_modified == null) 265 | warning ("BuildTask(%s) could not get last modified time of %s", id, file.get_path ()); 266 | else if (file_last_modified.compare (last_updated) > 0) { 267 | inputs_modified_after = true; 268 | break; 269 | } 270 | } 271 | 272 | if (!inputs_modified_after) 273 | return; 274 | } 275 | 276 | Subprocess process = launcher.spawnv (arguments); 277 | process.wait (cancellable); 278 | if (cancellable.is_cancelled ()) { 279 | process.force_exit (); 280 | cancellable.set_error_if_cancelled (); 281 | } else if (!process.get_successful ()) { 282 | string failed_msg = ""; 283 | if (process.get_if_exited ()) { 284 | failed_msg = @"BuildTask($id) `$exe_name' returned with status $(process.get_exit_status ()) (launched from $build_dir)"; 285 | } else { 286 | failed_msg = @"BuildTask($id) `$exe_name' terminated (launched from $build_dir)"; 287 | } 288 | // TODO: fix these Meson issues before enabling the following line: 289 | // 1. gnome.compile_resources() with depfile produces @DEPFILE@ in 290 | // targets without reference to depfle file (see plugins/files/meson.build in gitg) 291 | // 2. possible issue with how arguments to glib-compile-resources are introspected 292 | // when using gnome.compile_resources() (again, see plugins/files/meson.build in gitg) 293 | 294 | // throw new ProjectError.TASK_FAILED (failed_msg); 295 | warning (failed_msg); 296 | } else { 297 | if (output.size == 1 && !output[0].query_exists (cancellable)) { 298 | // write contents of stdout to the output file if it was not created 299 | var output_file = output[0]; 300 | var process_output_istream = process.get_stdout_pipe (); 301 | if (process_output_istream != null) { 302 | var outfile_ostream = output_file.replace (null, false, FileCreateFlags.NONE, cancellable); 303 | outfile_ostream.splice (process_output_istream, OutputStreamSpliceFlags.NONE, cancellable); 304 | } 305 | } 306 | // update the file metadata cache 307 | foreach (var file in output) 308 | _file_cache.update (file, cancellable); 309 | last_updated = new DateTime.now (); 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/util.vala: -------------------------------------------------------------------------------- 1 | /* util.vala 2 | * 3 | * Copyright 2020-2022 Princeton Ferro 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 2.1 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | using Gee; 20 | 21 | namespace Vls.Util { 22 | public static T? parse_variant (Variant variant) { 23 | var json = Json.gvariant_serialize (variant); 24 | return Json.gobject_deserialize (typeof (T), json); 25 | } 26 | 27 | public static Variant object_to_variant (Object object) throws Error { 28 | var json = Json.gobject_serialize (object); 29 | return Json.gvariant_deserialize (json, null); 30 | } 31 | 32 | /** 33 | * Gets the offset, in bytes, of the UTF-8 character at the given line and 34 | * position. 35 | * Both lineno and charno must be zero-indexed. 36 | */ 37 | public static size_t get_string_pos (string str, uint lineno, uint charno) { 38 | int pos = 0; 39 | unowned string curstr = str; 40 | 41 | for (uint lno = 0; lno < lineno; ++lno) { 42 | int rel_idx = curstr.index_of_char ('\n'); 43 | if (rel_idx == -1) 44 | break; 45 | pos += rel_idx; 46 | curstr = curstr.offset (rel_idx); 47 | if (curstr[1] != '\0') { 48 | // skip past the newline 49 | pos++; 50 | curstr = curstr.offset (1); 51 | } else { 52 | break; 53 | } 54 | } 55 | 56 | return pos + curstr.index_of_nth_char (charno); 57 | } 58 | 59 | /** 60 | * Gets the line and column of the pattern in [str]. Advances [lineno] and [charno] past the end of the pattern. 61 | */ 62 | public static void advance_past (string str, Regex pattern, ref uint lineno, ref uint charno) { 63 | MatchInfo match_info; 64 | if (pattern.match (str, 0, out match_info)) { 65 | int end_pos; 66 | if (match_info.fetch_pos (0, null, out end_pos)) { 67 | char *p; 68 | for (p = str; *p != '\0' && end_pos-- >= 0; p++) { 69 | if (is_newline (*p)) { 70 | lineno++; 71 | charno = 0; 72 | } else { 73 | charno++; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Parses arguments from a command string, taking care of escaped spaces 82 | * and single quotations. 83 | */ 84 | public static string[] get_arguments_from_command_str (string str) throws RegexError { 85 | MatchInfo match_info; 86 | string[] args = {}; 87 | 88 | // XXX: while this regex handles special cases, it can probably still be simplified, or transformed into a more-readable parser 89 | if (/(?(?<=')((\\\\|[^'\\\s]|\\')(\\\\|[^'\\]|\\')*(?='))|(?(?<=")((\\\\|[^"\\\s]|\\")(\\\\|[^"\\]|\\["abfnrtv])*(?="))|((?!["'])((?!\\ )(\\\\|[^\s;])|\\ ))+))/ 90 | .match (str, 0, out match_info)) { 91 | while (match_info.matches ()) { 92 | args += match_info.fetch (0); 93 | match_info.next (); 94 | } 95 | } 96 | 97 | return args; 98 | } 99 | 100 | public static int iterate_valac_args (string[] args, out string? flag_name, out string? arg_value, int last_arg_index) { 101 | last_arg_index = last_arg_index + 1; 102 | 103 | if (last_arg_index >= args.length) { 104 | flag_name = null; 105 | arg_value = null; 106 | return last_arg_index; 107 | } 108 | 109 | string param = args[last_arg_index]; 110 | 111 | do { 112 | MatchInfo match_info; 113 | if (/^--(\w*[\w-]*\w+)(=.+)?$/.match (param, 0, out match_info)) { 114 | // this is a lone flag 115 | flag_name = match_info.fetch (1); 116 | arg_value = match_info.fetch (2); 117 | 118 | if (arg_value != null && arg_value.length > 0) 119 | arg_value = arg_value.substring (1); 120 | 121 | if (arg_value == null || arg_value.length == 0) { 122 | arg_value = null; 123 | // depending on the type of flag, we may need to parse another argument, 124 | // since arg_value is NULL 125 | if (flag_name == "vapidir" || flag_name == "girdir" || 126 | flag_name == "metadatadir" || flag_name == "pkg" || 127 | flag_name == "vapi" || flag_name == "library" || 128 | flag_name == "shared-library" || flag_name == "gir" || 129 | flag_name == "basedir" || flag_name == "directory" || 130 | flag_name == "header" || flag_name == "includedir" || 131 | flag_name == "internal-header" || flag_name == "internal-vapi" || 132 | flag_name == "symbols" || flag_name == "output" || 133 | flag_name == "define" || flag_name == "main" || 134 | flag_name == "cc" || flag_name == "Xcc" || flag_name == "pkg-config" || 135 | flag_name == "dump-tree" || flag_name == "profile" || 136 | flag_name == "color" || flag_name == "target-glib" || 137 | flag_name == "gresources" || flag_name == "gresourcesdir") { 138 | if (last_arg_index < args.length - 1) 139 | last_arg_index++; 140 | arg_value = args[last_arg_index]; 141 | } 142 | } 143 | } else if (/^-(\w)(.+)?$/.match (param, 0, out match_info)) { 144 | string short_flag = match_info.fetch (1); 145 | string? short_arg = match_info.fetch (2); 146 | 147 | if (short_arg != null && short_arg.length == 0) 148 | short_arg = null; 149 | 150 | if (short_flag == "b") { 151 | param = "--basedir" + (short_arg != null ? @"=$short_arg" : ""); 152 | continue; 153 | } else if (short_flag == "d") { 154 | param = "--directory" + (short_arg != null ? @"=$short_arg" : ""); 155 | continue; 156 | } else if (short_flag == "C") { 157 | param = "--ccode"; 158 | continue; 159 | } else if (short_flag == "H") { 160 | param = "--header" + (short_arg != null ? @"=$short_arg" : ""); 161 | continue; 162 | } else if (short_flag == "h") { 163 | param = "--internal-header" + (short_arg != null ? @"=$short_arg" : ""); 164 | continue; 165 | } else if (short_flag == "c") { 166 | param = "--compile"; 167 | continue; 168 | } else if (short_flag == "o") { 169 | param = "--output" + (short_arg != null ? @"=$short_arg" : ""); 170 | continue; 171 | } else if (short_flag == "g") { 172 | param = "--debug"; 173 | continue; 174 | } else if (short_flag == "D") { 175 | param = "--define" + (short_arg != null ? @"=$short_arg" : ""); 176 | continue; 177 | } else if (short_flag == "k") { 178 | param = "--keep-going"; 179 | continue; 180 | } else if (short_flag == "X") { 181 | param = "--Xcc" + (short_arg != null ? @"=$short_arg" : ""); 182 | continue; 183 | } else if (short_flag == "q") { 184 | param = "--quiet"; 185 | continue; 186 | } else if (short_flag == "v") { 187 | param = "--verbose"; 188 | continue; 189 | } 190 | 191 | flag_name = null; 192 | arg_value = short_arg; 193 | } else { 194 | flag_name = null; 195 | arg_value = param; 196 | } 197 | break; 198 | } while (true); 199 | 200 | return last_arg_index; 201 | } 202 | 203 | public bool arg_is_vala_file (string arg) { 204 | return /^.*\.(vala|vapi|gir|gs)(\.in)?$/.match (arg); 205 | } 206 | 207 | static bool ends_with_dir_separator (string s) { 208 | return Path.is_dir_separator (s.get_char (s.length - 1)); 209 | } 210 | 211 | /** 212 | * Copied from libvala 213 | * 214 | * @see Vala.CodeContext.realpath 215 | */ 216 | public static string realpath (string name, string? cwd = null) { 217 | string rpath; 218 | 219 | // start of path component 220 | weak string start; 221 | // end of path component 222 | weak string end; 223 | 224 | if (!Path.is_absolute (name)) { 225 | // relative path 226 | rpath = cwd == null ? Environment.get_current_dir () : cwd; 227 | 228 | start = end = name; 229 | } else { 230 | // set start after root 231 | start = end = Path.skip_root (name); 232 | 233 | // extract root 234 | rpath = name.substring (0, (int) ((char*) start - (char*) name)); 235 | } 236 | 237 | long root_len = (long) ((char*) Path.skip_root (rpath) - (char*) rpath); 238 | 239 | for (; start.get_char () != 0; start = end) { 240 | // skip sequence of multiple path-separators 241 | while (Path.is_dir_separator (start.get_char ())) { 242 | start = start.next_char (); 243 | } 244 | 245 | // find end of path component 246 | long len = 0; 247 | for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) { 248 | len++; 249 | } 250 | 251 | if (len == 0) { 252 | break; 253 | } else if (len == 1 && start.get_char () == '.') { 254 | // do nothing 255 | } else if (len == 2 && start.has_prefix ("..")) { 256 | // back up to previous component, ignore if at root already 257 | if (rpath.length > root_len) { 258 | do { 259 | rpath = rpath.substring (0, rpath.length - 1); 260 | } while (!ends_with_dir_separator (rpath)); 261 | } 262 | } else { 263 | if (!ends_with_dir_separator (rpath)) { 264 | rpath += Path.DIR_SEPARATOR_S; 265 | } 266 | 267 | // don't use len, substring works on bytes 268 | rpath += start.substring (0, (long)((char*)end - (char*)start)); 269 | } 270 | } 271 | 272 | if (rpath.length > root_len && ends_with_dir_separator (rpath)) { 273 | rpath = rpath.substring (0, rpath.length - 1); 274 | } 275 | 276 | if (Path.DIR_SEPARATOR != '/') { 277 | // don't use backslashes internally, 278 | // to avoid problems in #include directives 279 | string[] components = rpath.split ("\\"); 280 | // casefold drive letters on Windows (c: -> C:) 281 | if (components.length > 0 && components[0].length > 1 && components[0].data[1] == ':') { 282 | components[0].data[0] = ((char)components[0].data[0]).toupper (); 283 | } 284 | rpath = string.joinv ("/", components); 285 | } 286 | 287 | return rpath; 288 | } 289 | 290 | /** 291 | * Like a cross-platform `rm -rf` 292 | */ 293 | public void remove_dir (string path) { 294 | try { 295 | var dir = File.new_for_path (path); 296 | FileEnumerator enumerator = dir.enumerate_children (FileAttribute.ID_FILE, FileQueryInfoFlags.NOFOLLOW_SYMLINKS); 297 | FileInfo? finfo; 298 | 299 | while ((finfo = enumerator.next_file ()) != null) { 300 | File child = dir.get_child (finfo.get_name ()); 301 | try { 302 | if (finfo.get_file_type () == FileType.DIRECTORY) 303 | remove_dir (child.get_path ()); 304 | else 305 | child.@delete (); 306 | } catch (Error e) { 307 | // ignore error 308 | } 309 | } 310 | dir.@delete (); 311 | } catch (Error e) { 312 | // ignore error 313 | } 314 | } 315 | 316 | public ArrayList find_files (File dir, Regex basename_pattern, 317 | uint max_depth = 1, Cancellable? cancellable = null, 318 | ArrayList found = new ArrayList ()) throws Error { 319 | assert (max_depth >= 1); 320 | FileEnumerator enumerator = dir.enumerate_children ( 321 | "standard::*", 322 | FileQueryInfoFlags.NOFOLLOW_SYMLINKS, 323 | cancellable); 324 | 325 | try { 326 | FileInfo? finfo; 327 | while ((finfo = enumerator.next_file (cancellable)) != null) { 328 | if (finfo.get_file_type () == FileType.DIRECTORY) { 329 | if (max_depth > 1) { 330 | find_files (enumerator.get_child (finfo), basename_pattern, max_depth - 1, cancellable, found); 331 | } 332 | } else if (basename_pattern.match (finfo.get_name ())) { 333 | found.add (enumerator.get_child (finfo)); 334 | } 335 | } 336 | } catch (Error e) { 337 | warning ("could not get next file in dir %s", dir.get_path ()); 338 | } 339 | 340 | return found; 341 | } 342 | 343 | public uint file_hash (File file) { 344 | string? path = file.get_path (); 345 | if (path != null) 346 | return realpath (path).hash (); 347 | return file.get_uri ().hash (); 348 | } 349 | 350 | public bool file_equal (File file1, File file2) { 351 | return file_hash (file1) == file_hash (file2); 352 | } 353 | 354 | public uint source_file_hash (Vala.SourceFile source_file) { 355 | return str_hash (source_file.filename); 356 | } 357 | 358 | public bool source_file_equal (Vala.SourceFile source_file1, Vala.SourceFile source_file2) { 359 | return source_file_hash (source_file1) == source_file_hash (source_file2); 360 | } 361 | 362 | public bool source_ref_equal (Vala.SourceReference source_ref1, Vala.SourceReference source_ref2) { 363 | return source_ref1.contains (source_ref2.begin) && source_ref1.contains (source_ref2.end) && 364 | source_ref2.contains (source_ref1.begin) && source_ref2.contains (source_ref1.end); 365 | } 366 | 367 | /** 368 | * (stolen from VersionAttribute.cmp_versions in `vala/valaversionattribute.vala`) 369 | * A simple version comparison function. 370 | * 371 | * @param v1str a version number 372 | * @param v2str a version number 373 | * @return an integer less than, equal to, or greater than zero, if v1str is <, == or > than v2str 374 | * @see GLib.CompareFunc 375 | */ 376 | public static int compare_versions (string v1str, string v2str) { 377 | string[] v1arr = v1str.split ("."); 378 | string[] v2arr = v2str.split ("."); 379 | int i = 0; 380 | 381 | while (v1arr[i] != null && v2arr[i] != null) { 382 | int v1num = int.parse (v1arr[i]); 383 | int v2num = int.parse (v2arr[i]); 384 | 385 | if (v1num < 0 || v2num < 0) { 386 | // invalid format 387 | return 0; 388 | } 389 | 390 | if (v1num > v2num) { 391 | return 1; 392 | } 393 | 394 | if (v1num < v2num) { 395 | return -1; 396 | } 397 | 398 | i++; 399 | } 400 | 401 | if (v1arr[i] != null && v2arr[i] == null) { 402 | return 1; 403 | } 404 | 405 | if (v1arr[i] == null && v2arr[i] != null) { 406 | return -1; 407 | } 408 | 409 | return 0; 410 | } 411 | 412 | /** 413 | * Counts the number of occurrences of @character in @str 414 | * 415 | * @param str the string to search 416 | * @param character the character to search for 417 | * @param last_char_pos the position of the last occurrence of @character in @str 418 | */ 419 | public uint count_chars_in_string (string str, char character, out int last_char_pos = null) { 420 | uint count = 0; 421 | last_char_pos = -1; 422 | for (int i = 0; i < str.length; i++) { 423 | if (str[i] == character) { 424 | count++; 425 | last_char_pos = i; 426 | } 427 | } 428 | return count; 429 | } 430 | 431 | private class GresourceParser { 432 | private const MarkupParser parser = { 433 | null, 434 | null, 435 | visit_text 436 | }; 437 | 438 | private MarkupParseContext context; 439 | 440 | private File[] source_dirs = {}; 441 | public File[] files = {}; 442 | 443 | private void visit_text (MarkupParseContext context, string text, size_t text_len) throws MarkupError { 444 | if (context.get_element () == "file") { 445 | foreach (var dir in source_dirs) { 446 | var child = dir.get_child (text); 447 | files += child; 448 | } 449 | } 450 | } 451 | 452 | public GresourceParser (File[] source_dirs) { 453 | this.source_dirs = source_dirs; 454 | context = new MarkupParseContext (parser, 0, this, null); 455 | } 456 | 457 | public void parse (string content) throws MarkupError { 458 | context.parse (content, -1); 459 | } 460 | } 461 | 462 | /** 463 | * Discover all {@link GLib.Resource}s that `glib-compile-resources` will 464 | * lookup when parsing `gresources_xml`. This includes files that may not 465 | * exist. 466 | * 467 | * @param gresources_xml Well-formed XML that would be parsed by `glib-compile-resources`. 468 | * @param source_dirs A list of source dirs to search in. If empty, the return array is guaranteed to be empty. 469 | * @return A list of files pointing to searched resources. 470 | */ 471 | public File[] discover_gresources_xml_input_files (string gresources_xml, File[] source_dirs) throws MarkupError { 472 | var parser = new GresourceParser (source_dirs); 473 | parser.parse (gresources_xml); 474 | return parser.files; 475 | } 476 | 477 | public bool is_newline (char character) { 478 | return character == '\n' || character == '\r'; 479 | } 480 | } 481 | --------------------------------------------------------------------------------