diff options
Diffstat (limited to 'src/shared/lsp')
51 files changed, 9797 insertions, 0 deletions
diff --git a/src/shared/lsp/.clang-tidy b/src/shared/lsp/.clang-tidy new file mode 100644 index 000000000..f451e9596 --- /dev/null +++ b/src/shared/lsp/.clang-tidy @@ -0,0 +1,3 @@ +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } diff --git a/src/shared/lsp/CMakeLists.txt b/src/shared/lsp/CMakeLists.txt new file mode 100644 index 000000000..1ed95db25 --- /dev/null +++ b/src/shared/lsp/CMakeLists.txt @@ -0,0 +1,57 @@ +add_qbs_library(qtclsp + STATIC + DEPENDS Qt${QT_VERSION_MAJOR}::Core Qt6Core5Compat + SOURCES + algorithm.h + basemessage.cpp + basemessage.h + callhierarchy.cpp + callhierarchy.h + client.cpp + client.h + clientcapabilities.cpp + clientcapabilities.h + completion.cpp + completion.h + diagnostics.cpp + diagnostics.h + initializemessages.cpp + initializemessages.h + jsonkeys.h + jsonobject.cpp + jsonobject.h + jsonrpcmessages.cpp + jsonrpcmessages.h + languagefeatures.cpp + languagefeatures.h + languageserverprotocol_global.h + languageserverprotocoltr.h + lsptypes.cpp + lsptypes.h + lsputils.cpp + lsputils.h + messages.cpp + messages.h + progresssupport.cpp + progresssupport.h + semantictokens.cpp + semantictokens.h + servercapabilities.cpp + servercapabilities.h + shutdownmessages.cpp + shutdownmessages.h + textsynchronization.cpp + textsynchronization.h + textutils.cpp + textutils.h + workspace.cpp + workspace.h + ) +target_include_directories( + qtclsp + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/corelib + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. +) +target_compile_definitions(qtclsp + PUBLIC "LANGUAGESERVERPROTOCOL_STATIC_LIBRARY" +) diff --git a/src/shared/lsp/algorithm.h b/src/shared/lsp/algorithm.h new file mode 100644 index 000000000..4c41fb649 --- /dev/null +++ b/src/shared/lsp/algorithm.h @@ -0,0 +1,1518 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "predicates.h" + +#include <qcompilerdetection.h> // for Q_REQUIRED_RESULT + +#include <algorithm> +#include <map> +#include <memory> +#include <set> +#include <tuple> +#include <unordered_map> +#include <unordered_set> + +#include <QHash> +#include <QObject> +#include <QSet> +#include <QStringList> + +#include <memory> +#include <optional> +#include <type_traits> + +namespace lsp::Utils { + +///////////////////////// +// anyOf +///////////////////////// +template<typename T, typename F> +bool anyOf(const T &container, F predicate); +template<typename T, typename R, typename S> +bool anyOf(const T &container, R (S::*predicate)() const); +template<typename T, typename R, typename S> +bool anyOf(const T &container, R S::*member); + +///////////////////////// +// count +///////////////////////// +template<typename T, typename F> +int count(const T &container, F predicate); + +///////////////////////// +// allOf +///////////////////////// +template<typename T, typename F> +bool allOf(const T &container, F predicate); + +///////////////////////// +// erase +///////////////////////// +template<typename T, typename F> +void erase(T &container, F predicate); +template<typename T, typename F> +bool eraseOne(T &container, F predicate); + +///////////////////////// +// contains +///////////////////////// +template<typename T, typename F> +bool contains(const T &container, F function); +template<typename T, typename R, typename S> +bool contains(const T &container, R (S::*function)() const); +template<typename C, typename R, typename S> +bool contains(const C &container, R S::*member); + +///////////////////////// +// findOr +///////////////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT typename C::value_type findOr(const C &container, + typename C::value_type other, + F function); +template<typename T, typename R, typename S> +Q_REQUIRED_RESULT typename T::value_type findOr(const T &container, + typename T::value_type other, + R (S::*function)() const); +template<typename T, typename R, typename S> +Q_REQUIRED_RESULT typename T::value_type findOr(const T &container, + typename T::value_type other, + R S::*member); + +///////////////////////// +// findOrDefault +///////////////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, + typename C::value_type> +findOrDefault(const C &container, F function); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, + typename C::value_type> +findOrDefault(const C &container, R (S::*function)() const); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, + typename C::value_type> +findOrDefault(const C &container, R S::*member); + +///////////////////////// +// indexOf +///////////////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT int indexOf(const C &container, F function); + +///////////////////////// +// maxElementOr +///////////////////////// +template<typename T> +typename T::value_type maxElementOr(const T &container, typename T::value_type other); + +///////////////////////// +// filtered +///////////////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT C filtered(const C &container, F predicate); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT C filtered(const C &container, R (S::*predicate)() const); + +///////////////////////// +// partition +///////////////////////// +// Recommended usage: +// C hit; +// C miss; +// std::tie(hit, miss) = Utils::partition(container, predicate); +template<typename C, typename F> +Q_REQUIRED_RESULT std::tuple<C, C> partition(const C &container, F predicate); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT std::tuple<C, C> partition(const C &container, R (S::*predicate)() const); + +///////////////////////// +// filteredUnique +///////////////////////// +template<typename C> +Q_REQUIRED_RESULT C filteredUnique(const C &container); + +///////////////////////// +// qobject_container_cast +///////////////////////// +template<class T, template<typename> class Container, typename Base> +Container<T> qobject_container_cast(const Container<Base> &container); + +///////////////////////// +// static_container_cast +///////////////////////// +template<class T, template<typename> class Container, typename Base> +Container<T> static_container_cast(const Container<Base> &container); + +///////////////////////// +// sort +///////////////////////// +template<typename Container> +inline void sort(Container &container); +template<typename Container, typename Predicate> +inline void sort(Container &container, Predicate p); +template<typename Container, typename R, typename S> +inline void sort(Container &container, R S::*member); +template<typename Container, typename R, typename S> +inline void sort(Container &container, R (S::*function)() const); + +///////////////////////// +// reverseForeach +///////////////////////// +template<typename Container, typename Op> +inline void reverseForeach(const Container &c, const Op &operation); + +///////////////////////// +// toReferences +///////////////////////// +template<template<typename...> class ResultContainer, typename SourceContainer> +auto toReferences(SourceContainer &sources); +template<typename SourceContainer> +auto toReferences(SourceContainer &sources); + +///////////////////////// +// toConstReferences +///////////////////////// +template<template<typename...> class ResultContainer, typename SourceContainer> +auto toConstReferences(const SourceContainer &sources); +template<typename SourceContainer> +auto toConstReferences(const SourceContainer &sources); + +///////////////////////// +// take +///////////////////////// +template<class C, typename P> +Q_REQUIRED_RESULT std::optional<typename C::value_type> take(C &container, P predicate); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT decltype(auto) take(C &container, R S::*member); +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT decltype(auto) take(C &container, R (S::*function)() const); + +///////////////////////// +// setUnionMerge +///////////////////////// +// Works like std::set_union but provides a merge function for items that match +// !(a > b) && !(b > a) which normally means that there is an "equal" match. +// It uses iterators to support move_iterators. +template<class InputIt1, class InputIt2, class OutputIt, class Merge, class Compare> +OutputIt setUnionMerge(InputIt1 first1, + InputIt1 last1, + InputIt2 first2, + InputIt2 last2, + OutputIt d_first, + Merge merge, + Compare comp); +template<class InputIt1, class InputIt2, class OutputIt, class Merge> +OutputIt setUnionMerge( + InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Merge merge); +template<class OutputContainer, class InputContainer1, class InputContainer2, class Merge, class Compare> +OutputContainer setUnionMerge(InputContainer1 &&input1, + InputContainer2 &&input2, + Merge merge, + Compare comp); +template<class OutputContainer, class InputContainer1, class InputContainer2, class Merge> +OutputContainer setUnionMerge(InputContainer1 &&input1, InputContainer2 &&input2, Merge merge); + +///////////////////////// +// setUnion +///////////////////////// +template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare> +OutputIterator set_union(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + OutputIterator result, + Compare comp); +template<typename InputIterator1, typename InputIterator2, typename OutputIterator> +OutputIterator set_union(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + OutputIterator result); + +///////////////////////// +// transform +///////////////////////// +// function without result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container type + typename F> // function type +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function); + +// function with result type deduction: +template<template<typename> class C, // result container type + typename SC, // input container type + typename F, // function type + typename Value = typename std::decay_t<SC>::value_type, + typename Result = std::decay_t<std::result_of_t<F(Value &)>>, + typename ResultContainer = C<Result>> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function); +#ifdef Q_CC_CLANG +// "Matching of template template-arguments excludes compatible templates" +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0522r0.html (P0522R0) +// in C++17 makes the above match e.g. C=std::vector even though that takes two +// template parameters. Unfortunately the following one matches too, and there is no additional +// partial ordering rule, resulting in an ambiguous call for this previously valid code. +// GCC and MSVC ignore that issue and follow the standard to the letter, but Clang only +// enables the new behavior when given -frelaxed-template-template-args . +// To avoid requiring everyone using this header to enable that feature, keep the old implementation +// for Clang. +template<template<typename, typename> class C, // result container type + typename SC, // input container type + typename F, // function type + typename Value = typename std::decay_t<SC>::value_type, + typename Result = std::decay_t<std::result_of_t<F(Value &)>>, + typename ResultContainer = C<Result, std::allocator<Result>>> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function); +#endif + +// member function without result type deduction: +template<template<typename...> class C, // result container type + typename SC, // input container type + typename R, + typename S> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R (S::*p)() const); + +// member function with result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container type + typename R, + typename S> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R (S::*p)() const); + +// member without result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container + typename R, + typename S> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R S::*p); + +// member with result type deduction: +template<template<typename...> class C, // result container + typename SC, // input container + typename R, + typename S> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R S::*p); + +// same container types for input and output, const input +// function: +template<template<typename...> class C, // container type + typename F, // function type + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, F function); + +// same container types for input and output, const input +// member function: +template<template<typename...> class C, // container type + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, R (S::*p)() const); + +// same container types for input and output, const input +// members: +template<template<typename...> class C, // container + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, R S::*p); + +// same container types for input and output, non-const input +// function: +template<template<typename...> class C, // container type + typename F, // function type + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, F function); + +// same container types for input and output, non-const input +// member function: +template<template<typename...> class C, // container type + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, R (S::*p)() const); + +// same container types for input and output, non-const input +// members: +template<template<typename...> class C, // container + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, R S::*p); + +///////////////////////////////////////////////////////////////////////////// +//////// Implementations ////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +////////////////// +// anyOf +///////////////// +template<typename T, typename F> +bool anyOf(const T &container, F predicate) +{ + return std::any_of(std::begin(container), std::end(container), predicate); +} + +// anyOf taking a member function pointer +template<typename T, typename R, typename S> +bool anyOf(const T &container, R (S::*predicate)() const) +{ + return std::any_of(std::begin(container), std::end(container), std::mem_fn(predicate)); +} + +// anyOf taking a member pointer +template<typename T, typename R, typename S> +bool anyOf(const T &container, R S::*member) +{ + return std::any_of(std::begin(container), std::end(container), std::mem_fn(member)); +} + + +////////////////// +// count +///////////////// +template<typename T, typename F> +int count(const T &container, F predicate) +{ + return std::count_if(std::begin(container), std::end(container), predicate); +} + +////////////////// +// allOf +///////////////// +template<typename T, typename F> +bool allOf(const T &container, F predicate) +{ + return std::all_of(std::begin(container), std::end(container), predicate); +} + +// allOf taking a member function pointer +template<typename T, typename R, typename S> +bool allOf(const T &container, R (S::*predicate)() const) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(predicate)); +} + +// allOf taking a member pointer +template<typename T, typename R, typename S> +bool allOf(const T &container, R S::*member) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(member)); +} + +////////////////// +// erase +///////////////// +template<typename T, typename F> +void erase(T &container, F predicate) +{ + container.erase(std::remove_if(std::begin(container), std::end(container), predicate), + std::end(container)); +} +template<typename T, typename F> +bool eraseOne(T &container, F predicate) +{ + const auto it = std::find_if(std::begin(container), std::end(container), predicate); + if (it == std::end(container)) + return false; + container.erase(it); + return true; +} + +////////////////// +// contains +///////////////// +template<typename T, typename F> +bool contains(const T &container, F function) +{ + return anyOf(container, function); +} + +template<typename T, typename R, typename S> +bool contains(const T &container, R (S::*function)() const) +{ + return anyOf(container, function); +} + +template<typename C, typename R, typename S> +bool contains(const C &container, R S::*member) +{ + return anyOf(container, std::mem_fn(member)); +} + +////////////////// +// findOr +///////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT +typename C::value_type findOr(const C &container, typename C::value_type other, F function) +{ + typename C::const_iterator begin = std::begin(container); + typename C::const_iterator end = std::end(container); + + typename C::const_iterator it = std::find_if(begin, end, function); + return it == end ? other : *it; +} + +template<typename T, typename R, typename S> +Q_REQUIRED_RESULT +typename T::value_type findOr(const T &container, typename T::value_type other, R (S::*function)() const) +{ + return findOr(container, other, std::mem_fn(function)); +} + +template<typename T, typename R, typename S> +Q_REQUIRED_RESULT +typename T::value_type findOr(const T &container, typename T::value_type other, R S::*member) +{ + return findOr(container, other, std::mem_fn(member)); +} + +////////////////// +// findOrDefault +////////////////// +// Default implementation: +template<typename C, typename F> +Q_REQUIRED_RESULT +typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type> +findOrDefault(const C &container, F function) +{ + return findOr(container, typename C::value_type(), function); +} + +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT +typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type> +findOrDefault(const C &container, R (S::*function)() const) +{ + return findOr(container, typename C::value_type(), std::mem_fn(function)); +} + +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT +typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type> +findOrDefault(const C &container, R S::*member) +{ + return findOr(container, typename C::value_type(), std::mem_fn(member)); +} + +////////////////// +// index of: +////////////////// + +template<typename C, typename F> +Q_REQUIRED_RESULT +int indexOf(const C& container, F function) +{ + typename C::const_iterator begin = std::begin(container); + typename C::const_iterator end = std::end(container); + + typename C::const_iterator it = std::find_if(begin, end, function); + return it == end ? -1 : std::distance(begin, it); +} + + +////////////////// +// max element +////////////////// + +template<typename T> +typename T::value_type maxElementOr(const T &container, typename T::value_type other) +{ + typename T::const_iterator begin = std::begin(container); + typename T::const_iterator end = std::end(container); + + typename T::const_iterator it = std::max_element(begin, end); + if (it == end) + return other; + return *it; +} + + +////////////////// +// transform +///////////////// + +namespace { +///////////////// +// helper code for transform to use back_inserter and thus push_back for everything +// and insert for QSet<> +// + +// SetInsertIterator, straight from the standard for insert_iterator +// just without the additional parameter to insert +template<class Container> +class SetInsertIterator +{ +protected: + Container *container; + +public: + using iterator_category = std::output_iterator_tag; + using container_type = Container; + explicit SetInsertIterator(Container &x) + : container(&x) + {} + SetInsertIterator<Container> &operator=(const typename Container::value_type &value) + { + container->insert(value); + return *this; + } + SetInsertIterator<Container> &operator=(typename Container::value_type &&value) + { + container->insert(std::move(value)); + return *this; + } + SetInsertIterator<Container> &operator*() { return *this; } + SetInsertIterator<Container> &operator++() { return *this; } + SetInsertIterator<Container> operator++(int) { return *this; } +}; + +// for QMap / QHash, inserting a std::pair / QPair +template<class Container> +class MapInsertIterator +{ +protected: + Container *container; + +public: + using iterator_category = std::output_iterator_tag; + using container_type = Container; + explicit MapInsertIterator(Container &x) + : container(&x) + {} + MapInsertIterator<Container> &operator=( + const std::pair<const typename Container::key_type, typename Container::mapped_type> &value) + { container->insert(value.first, value.second); return *this; } + MapInsertIterator<Container> &operator=( + const QPair<typename Container::key_type, typename Container::mapped_type> &value) + { container->insert(value.first, value.second); return *this; } + MapInsertIterator<Container> &operator*() { return *this; } + MapInsertIterator<Container> &operator++() { return *this; } + MapInsertIterator<Container> operator++(int) { return *this; } +}; + +// because Qt container are not implementing the standard interface we need +// this helper functions for generic code +template<typename Type> +void append(QList<Type> *container, QList<Type> &&input) +{ + container->append(std::move(input)); +} + +template<typename Type> +void append(QList<Type> *container, const QList<Type> &input) +{ + container->append(input); +} + +template<typename Container> +void append(Container *container, Container &&input) +{ + container->insert(container->end(), + std::make_move_iterator(input.begin()), + std::make_move_iterator(input.end())); +} + +template<typename Container> +void append(Container *container, const Container &input) +{ + container->insert(container->end(), input.begin(), input.end()); +} + +// BackInsertIterator behaves like std::back_insert_iterator except is adds the back insertion for +// container of the same type +template<typename Container> +class BackInsertIterator +{ +public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = ptrdiff_t; + using pointer = void; + using reference = void; + using container_type = Container; + + explicit constexpr BackInsertIterator(Container &container) + : m_container(std::addressof(container)) + {} + + constexpr BackInsertIterator &operator=(const typename Container::value_type &value) + { + m_container->push_back(value); + return *this; + } + + constexpr BackInsertIterator &operator=(typename Container::value_type &&value) + { + m_container->push_back(std::move(value)); + return *this; + } + + constexpr BackInsertIterator &operator=(const Container &container) + { + append(m_container, container); + return *this; + } + + constexpr BackInsertIterator &operator=(Container &&container) + { + append(m_container, container); + return *this; + } + + [[nodiscard]] constexpr BackInsertIterator &operator*() { return *this; } + + constexpr BackInsertIterator &operator++() { return *this; } + + constexpr BackInsertIterator operator++(int) { return *this; } + +private: + Container *m_container; +}; + +// inserter helper function, returns a BackInsertIterator for most containers +// and is overloaded for QSet<> and other containers without push_back, returning custom inserters +template<typename Container> +inline BackInsertIterator<Container> inserter(Container &container) +{ + return BackInsertIterator(container); +} + +template<typename X> +inline SetInsertIterator<QSet<X>> +inserter(QSet<X> &container) +{ + return SetInsertIterator<QSet<X>>(container); +} + +template<typename K, typename C, typename A> +inline SetInsertIterator<std::set<K, C, A>> +inserter(std::set<K, C, A> &container) +{ + return SetInsertIterator<std::set<K, C, A>>(container); +} + +template<typename K, typename H, typename C, typename A> +inline SetInsertIterator<std::unordered_set<K, H, C, A>> +inserter(std::unordered_set<K, H, C, A> &container) +{ + return SetInsertIterator<std::unordered_set<K, H, C, A>>(container); +} + +template<typename K, typename V, typename C, typename A> +inline SetInsertIterator<std::map<K, V, C, A>> +inserter(std::map<K, V, C, A> &container) +{ + return SetInsertIterator<std::map<K, V, C, A>>(container); +} + +template<typename K, typename V, typename H, typename C, typename A> +inline SetInsertIterator<std::unordered_map<K, V, H, C, A>> +inserter(std::unordered_map<K, V, H, C, A> &container) +{ + return SetInsertIterator<std::unordered_map<K, V, H, C, A>>(container); +} + +template<typename K, typename V> +inline MapInsertIterator<QMap<K, V>> +inserter(QMap<K, V> &container) +{ + return MapInsertIterator<QMap<K, V>>(container); +} + +template<typename K, typename V> +inline MapInsertIterator<QHash<K, V>> +inserter(QHash<K, V> &container) +{ + return MapInsertIterator<QHash<K, V>>(container); +} + +// Helper code for container.reserve that makes it possible to effectively disable it for +// specific cases + +// default: do reserve +// Template arguments are more specific than the second version below, so this is tried first +template<template<typename...> class C, typename... CArgs, + typename = decltype(&C<CArgs...>::reserve)> +void reserve(C<CArgs...> &c, typename C<CArgs...>::size_type s) +{ + c.reserve(s); +} + +// containers that don't have reserve() +template<typename C> +void reserve(C &, typename C::size_type) { } + +} // anonymous + +// -------------------------------------------------------------------- +// Different containers for input and output: +// -------------------------------------------------------------------- + +// different container types for input and output, e.g. transforming a QList into a QSet + +// function without result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container type + typename F> // function type +Q_REQUIRED_RESULT +decltype(auto) transform(SC &&container, F function) +{ + ResultContainer result; + reserve(result, typename ResultContainer::size_type(container.size())); + std::transform(std::begin(container), std::end(container), inserter(result), function); + return result; +} + +// function with result type deduction: +template<template<typename> class C, // result container type + typename SC, // input container type + typename F, // function type + typename Value, + typename Result, + typename ResultContainer> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function) +{ + return transform<ResultContainer>(std::forward<SC>(container), function); +} + +#ifdef Q_CC_CLANG +template<template<typename, typename> class C, // result container type + typename SC, // input container type + typename F, // function type + typename Value, + typename Result, + typename ResultContainer> +Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function) +{ + return transform<ResultContainer>(std::forward<SC>(container), function); +} +#endif + +// member function without result type deduction: +template<template<typename...> class C, // result container type + typename SC, // input container type + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(SC &&container, R (S::*p)() const) +{ + return transform<C>(std::forward<SC>(container), std::mem_fn(p)); +} + +// member function with result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container type + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(SC &&container, R (S::*p)() const) +{ + return transform<ResultContainer>(std::forward<SC>(container), std::mem_fn(p)); +} + +// member without result type deduction: +template<typename ResultContainer, // complete result container type + typename SC, // input container + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(SC &&container, R S::*p) +{ + return transform<ResultContainer>(std::forward<SC>(container), std::mem_fn(p)); +} + +// member with result type deduction: +template<template<typename...> class C, // result container + typename SC, // input container + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(SC &&container, R S::*p) +{ + return transform<C>(std::forward<SC>(container), std::mem_fn(p)); +} + +// same container types for input and output, const input + +// function: +template<template<typename...> class C, // container type + typename F, // function type + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(const C<CArgs...> &container, F function) +{ + return transform<C, const C<CArgs...> &>(container, function); +} + +// member function: +template<template<typename...> class C, // container type + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(const C<CArgs...> &container, R (S::*p)() const) +{ + return transform<C, const C<CArgs...> &>(container, std::mem_fn(p)); +} + +// members: +template<template<typename...> class C, // container + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(const C<CArgs...> &container, R S::*p) +{ + return transform<C, const C<CArgs...> &>(container, std::mem_fn(p)); +} + +// same container types for input and output, non-const input + +// function: +template<template<typename...> class C, // container type + typename F, // function type + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(C<CArgs...> &container, F function) +{ + return transform<C, C<CArgs...> &>(container, function); +} + +// member function: +template<template<typename...> class C, // container type + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(C<CArgs...> &container, R (S::*p)() const) +{ + return transform<C, C<CArgs...> &>(container, std::mem_fn(p)); +} + +// members: +template<template<typename...> class C, // container + typename R, + typename S, + typename... CArgs> // Arguments to SC +Q_REQUIRED_RESULT +decltype(auto) transform(C<CArgs...> &container, R S::*p) +{ + return transform<C, C<CArgs...> &>(container, std::mem_fn(p)); +} + +// Specialization for QStringList: + +template<template<typename...> class C = QList, // result container + typename F> // Arguments to C +Q_REQUIRED_RESULT +decltype(auto) transform(const QStringList &container, F function) +{ + return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), function); +} + +// member function: +template<template<typename...> class C = QList, // result container type + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(const QStringList &container, R (S::*p)() const) +{ + return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), std::mem_fn(p)); +} + +// members: +template<template<typename...> class C = QList, // result container + typename R, + typename S> +Q_REQUIRED_RESULT +decltype(auto) transform(const QStringList &container, R S::*p) +{ + return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), std::mem_fn(p)); +} + +////////////////// +// filtered +///////////////// +template<typename C, typename F> +Q_REQUIRED_RESULT +C filtered(const C &container, F predicate) +{ + C out; + std::copy_if(std::begin(container), std::end(container), + inserter(out), predicate); + return out; +} + +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT +C filtered(const C &container, R (S::*predicate)() const) +{ + C out; + std::copy_if(std::begin(container), std::end(container), + inserter(out), std::mem_fn(predicate)); + return out; +} + +////////////////// +// filteredCast +///////////////// +template<typename R, typename C, typename F> +Q_REQUIRED_RESULT R filteredCast(const C &container, F predicate) +{ + R out; + std::copy_if(std::begin(container), std::end(container), inserter(out), predicate); + return out; +} + +////////////////// +// partition +///////////////// + +// Recommended usage: +// C hit; +// C miss; +// std::tie(hit, miss) = Utils::partition(container, predicate); + +template<typename C, typename F> +Q_REQUIRED_RESULT +std::tuple<C, C> partition(const C &container, F predicate) +{ + C hit; + C miss; + reserve(hit, container.size()); + reserve(miss, container.size()); + auto hitIns = inserter(hit); + auto missIns = inserter(miss); + for (const auto &i : container) { + if (predicate(i)) + hitIns = i; + else + missIns = i; + } + return std::make_tuple(hit, miss); +} + +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT +std::tuple<C, C> partition(const C &container, R (S::*predicate)() const) +{ + return partition(container, std::mem_fn(predicate)); +} + +////////////////// +// filteredUnique +///////////////// + +template<typename C> +Q_REQUIRED_RESULT +C filteredUnique(const C &container) +{ + C result; + auto ins = inserter(result); + + QSet<typename C::value_type> seen; + int setSize = 0; + + auto endIt = std::end(container); + for (auto it = std::begin(container); it != endIt; ++it) { + seen.insert(*it); + if (setSize == seen.size()) // unchanged size => was already seen + continue; + ++setSize; + ins = *it; + } + return result; +} + +////////////////// +// qobject_container_cast +///////////////// +template <class T, template<typename> class Container, typename Base> +Container<T> qobject_container_cast(const Container<Base> &container) +{ + Container<T> result; + auto ins = inserter(result); + for (Base val : container) { + if (T target = qobject_cast<T>(val)) + ins = target; + } + return result; +} + +////////////////// +// static_container_cast +///////////////// +template <class T, template<typename> class Container, typename Base> +Container<T> static_container_cast(const Container<Base> &container) +{ + Container<T> result; + reserve(result, container.size()); + auto ins = inserter(result); + for (Base val : container) + ins = static_cast<T>(val); + return result; +} + +////////////////// +// sort +///////////////// +template <typename Container> +inline void sort(Container &container) +{ + std::stable_sort(std::begin(container), std::end(container)); +} + +template <typename Container, typename Predicate> +inline void sort(Container &container, Predicate p) +{ + std::stable_sort(std::begin(container), std::end(container), p); +} + +// const lvalue +template<typename Container> +inline Container sorted(const Container &container) +{ + Container c = container; + sort(c); + return c; +} + +// non-const lvalue +// This is needed because otherwise the "universal" reference below is used, modifying the input +// container. +template<typename Container> +inline Container sorted(Container &container) +{ + Container c = container; + sort(c); + return c; +} + +// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above) +template<typename Container> +inline Container sorted(Container &&container) +{ + sort(container); + return std::move(container); +} + +// const rvalue +template<typename Container> +inline Container sorted(const Container &&container) +{ + return sorted(container); +} + +// const lvalue +template<typename Container, typename Predicate> +inline Container sorted(const Container &container, Predicate p) +{ + Container c = container; + sort(c, p); + return c; +} + +// non-const lvalue +// This is needed because otherwise the "universal" reference below is used, modifying the input +// container. +template<typename Container, typename Predicate> +inline Container sorted(Container &container, Predicate p) +{ + Container c = container; + sort(c, p); + return c; +} + +// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above) +template<typename Container, typename Predicate> +inline Container sorted(Container &&container, Predicate p) +{ + sort(container, p); + return std::move(container); +} + +// const rvalue +template<typename Container, typename Predicate> +inline Container sorted(const Container &&container, Predicate p) +{ + return sorted(container, p); +} + +// pointer to member +template<typename Container, typename R, typename S> +inline void sort(Container &container, R S::*member) +{ + auto f = std::mem_fn(member); + using const_ref = typename Container::const_reference; + std::stable_sort(std::begin(container), std::end(container), + [&f](const_ref a, const_ref b) { + return f(a) < f(b); + }); +} + +// const lvalue +template<typename Container, typename R, typename S> +inline Container sorted(const Container &container, R S::*member) +{ + Container c = container; + sort(c, member); + return c; +} + +// non-const lvalue +// This is needed because otherwise the "universal" reference below is used, modifying the input +// container. +template<typename Container, typename R, typename S> +inline Container sorted(Container &container, R S::*member) +{ + Container c = container; + sort(c, member); + return c; +} + +// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above) +template<typename Container, typename R, typename S> +inline Container sorted(Container &&container, R S::*member) +{ + sort(container, member); + return std::move(container); +} + +// const rvalue +template<typename Container, typename R, typename S> +inline Container sorted(const Container &&container, R S::*member) +{ + return sorted(container, member); +} + +// pointer to member function +template<typename Container, typename R, typename S> +inline void sort(Container &container, R (S::*function)() const) +{ + auto f = std::mem_fn(function); + using const_ref = typename Container::const_reference; + std::stable_sort(std::begin(container), std::end(container), + [&f](const_ref a, const_ref b) { + return f(a) < f(b); + }); +} + +// const lvalue +template<typename Container, typename R, typename S> +inline Container sorted(const Container &container, R (S::*function)() const) +{ + Container c = container; + sort(c, function); + return c; +} + +// non-const lvalue +// This is needed because otherwise the "universal" reference below is used, modifying the input +// container. +template<typename Container, typename R, typename S> +inline Container sorted(Container &container, R (S::*function)() const) +{ + Container c = container; + sort(c, function); + return c; +} + +// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above) +template<typename Container, typename R, typename S> +inline Container sorted(Container &&container, R (S::*function)() const) +{ + sort(container, function); + return std::move(container); +} + +// const rvalue +template<typename Container, typename R, typename S> +inline Container sorted(const Container &&container, R (S::*function)() const) +{ + return sorted(container, function); +} + +////////////////// +// reverseForeach +///////////////// +template <typename Container, typename Op> +inline void reverseForeach(const Container &c, const Op &operation) +{ + auto rend = c.rend(); + for (auto it = c.rbegin(); it != rend; ++it) + operation(*it); +} + +////////////////// +// toReferences +///////////////// +template <template<typename...> class ResultContainer, + typename SourceContainer> +auto toReferences(SourceContainer &sources) +{ + return transform<ResultContainer>(sources, [] (auto &value) { return std::ref(value); }); +} + +template <typename SourceContainer> +auto toReferences(SourceContainer &sources) +{ + return transform(sources, [] (auto &value) { return std::ref(value); }); +} + +////////////////// +// toConstReferences +///////////////// +template <template<typename...> class ResultContainer, + typename SourceContainer> +auto toConstReferences(const SourceContainer &sources) +{ + return transform<ResultContainer>(sources, [] (const auto &value) { return std::cref(value); }); +} + +template <typename SourceContainer> +auto toConstReferences(const SourceContainer &sources) +{ + return transform(sources, [] (const auto &value) { return std::cref(value); }); +} + +////////////////// +// take: +///////////////// + +template<class C, typename P> +Q_REQUIRED_RESULT std::optional<typename C::value_type> take(C &container, P predicate) +{ + const auto end = std::end(container); + + const auto it = std::find_if(std::begin(container), end, predicate); + if (it == end) + return std::nullopt; + + std::optional<typename C::value_type> result = std::make_optional(std::move(*it)); + container.erase(it); + return result; +} + +// pointer to member +template <typename C, typename R, typename S> +Q_REQUIRED_RESULT decltype(auto) take(C &container, R S::*member) +{ + return take(container, std::mem_fn(member)); +} + +// pointer to member function +template <typename C, typename R, typename S> +Q_REQUIRED_RESULT decltype(auto) take(C &container, R (S::*function)() const) +{ + return take(container, std::mem_fn(function)); +} + +////////////////// +// setUnionMerge: Works like std::set_union but provides a merge function for items that match +// !(a > b) && !(b > a) which normally means that there is an "equal" match. +// It uses iterators to support move_iterators. +///////////////// + +template<class InputIt1, + class InputIt2, + class OutputIt, + class Merge, + class Compare> +OutputIt setUnionMerge(InputIt1 first1, + InputIt1 last1, + InputIt2 first2, + InputIt2 last2, + OutputIt d_first, + Merge merge, + Compare comp) +{ + for (; first1 != last1; ++d_first) { + if (first2 == last2) + return std::copy(first1, last1, d_first); + if (comp(*first2, *first1)) { + *d_first = *first2++; + } else { + if (comp(*first1, *first2)) { + *d_first = *first1; + } else { + *d_first = merge(*first1, *first2); + ++first2; + } + ++first1; + } + } + return std::copy(first2, last2, d_first); +} + +template<class InputIt1, + class InputIt2, + class OutputIt, + class Merge> +OutputIt setUnionMerge(InputIt1 first1, + InputIt1 last1, + InputIt2 first2, + InputIt2 last2, + OutputIt d_first, + Merge merge) +{ + return setUnionMerge(first1, + last1, + first2, + last2, + d_first, + merge, + std::less<std::decay_t<decltype(*first1)>>{}); +} + +template<class OutputContainer, + class InputContainer1, + class InputContainer2, + class Merge, + class Compare> +OutputContainer setUnionMerge(InputContainer1 &&input1, + InputContainer2 &&input2, + Merge merge, + Compare comp) +{ + OutputContainer results; + results.reserve(input1.size() + input2.size()); + + setUnionMerge(std::make_move_iterator(std::begin(input1)), + std::make_move_iterator(std::end(input1)), + std::make_move_iterator(std::begin(input2)), + std::make_move_iterator(std::end(input2)), + std::back_inserter(results), + merge, + comp); + + return results; +} + +template<class OutputContainer, + class InputContainer1, + class InputContainer2, + class Merge> +OutputContainer setUnionMerge(InputContainer1 &&input1, + InputContainer2 &&input2, + Merge merge) +{ + return setUnionMerge<OutputContainer>(std::forward<InputContainer1>(input1), + std::forward<InputContainer2>(input2), + merge, + std::less<std::decay_t<decltype(*std::begin(input1))>>{}); +} + +template<typename Container> +auto usize(const Container &container) +{ + return static_cast<std::make_unsigned_t<decltype(std::size(container))>>(std::size(container)); +} + +template<typename Container> +auto ssize(const Container &container) +{ + return static_cast<std::make_signed_t<decltype(std::size(container))>>(std::size(container)); +} + +template<typename Compare> +struct CompareIter +{ + Compare compare; + + explicit constexpr CompareIter(Compare compare) + : compare(std::move(compare)) + {} + + template<typename Iterator1, typename Iterator2> + constexpr bool operator()(Iterator1 it1, Iterator2 it2) + { + return bool(compare(*it1, *it2)); + } +}; + +template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare> +OutputIterator set_union_impl(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + OutputIterator result, + Compare comp) +{ + auto compare = CompareIter<Compare>(comp); + + while (first1 != last1 && first2 != last2) { + if (compare(first1, first2)) { + *result = *first1; + ++first1; + } else if (compare(first2, first1)) { + *result = *first2; + ++first2; + } else { + *result = *first1; + ++first1; + ++first2; + } + ++result; + } + + return std::copy(first2, last2, std::copy(first1, last1, result)); +} + +template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare> +OutputIterator set_union(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + OutputIterator result, + Compare comp) +{ + return set_union_impl(first1, last1, first2, last2, result, comp); +} + +template<typename InputIterator1, typename InputIterator2, typename OutputIterator> +OutputIterator set_union(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + OutputIterator result) +{ + return set_union_impl( + first1, last1, first2, last2, result, std::less<typename InputIterator1::value_type>{}); +} + +// Replacement for deprecated Qt functionality + +template <class T> +QSet<T> toSet(const QList<T> &list) +{ + return QSet<T>(list.begin(), list.end()); +} + +template<class T> +QList<T> toList(const QSet<T> &set) +{ + return QList<T>(set.begin(), set.end()); +} + +template <class Key, class T> +void addToHash(QHash<Key, T> *result, const QHash<Key, T> &additionalContents) +{ + result->insert(additionalContents); +} + +// Workaround for missing information from QSet::insert() +// Return type could be a pair like for std::set, but we never use the iterator anyway. +template<typename T, typename U> [[nodiscard]] bool insert(QSet<T> &s, const U &v) +{ + const int oldSize = s.size(); + s.insert(v); + return s.size() > oldSize; +} + +} // namespace lsp::Utils diff --git a/src/shared/lsp/basemessage.cpp b/src/shared/lsp/basemessage.cpp new file mode 100644 index 000000000..4b19190dc --- /dev/null +++ b/src/shared/lsp/basemessage.cpp @@ -0,0 +1,188 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "basemessage.h" + +#include "jsonrpcmessages.h" +#include "languageserverprotocoltr.h" + +#include <QBuffer> +#include <QTextCodec> + +#include <cstring> +#include <utility> + +namespace lsp { + +Q_LOGGING_CATEGORY(parseLog, "qtc.languageserverprotocol.parse", QtWarningMsg) + +constexpr char headerFieldSeparator[] = ": "; +constexpr char contentCharsetName[] = "charset"; +constexpr char defaultCharset[] = "utf-8"; +constexpr char contentLengthFieldName[] = "Content-Length"; +constexpr char headerSeparator[] = "\r\n"; +constexpr char contentTypeFieldName[] = "Content-Type"; + +BaseMessage::BaseMessage() + : mimeType(JsonRpcMessage::jsonRpcMimeType()) +{ } + +BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content, + int expectedLength, QTextCodec *codec) + : mimeType(mimeType.isEmpty() ? JsonRpcMessage::jsonRpcMimeType() : mimeType) + , content(content) + , contentLength(expectedLength) + , codec(codec) +{ } + +BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content) + : BaseMessage(mimeType, content, content.length(), defaultCodec()) +{ } + +bool BaseMessage::operator==(const BaseMessage &other) const +{ + if (mimeType != other.mimeType || content != other.content) + return false; + if (codec) { + if (other.codec) + return codec->mibEnum() == other.codec->mibEnum(); + return codec->mibEnum() == defaultCodec()->mibEnum(); + } + if (other.codec) + return other.codec->mibEnum() == defaultCodec()->mibEnum(); + + return true; +} + +static QPair<QByteArray, QByteArray> splitHeaderFieldLine(const QByteArray &headerFieldLine) +{ + static const int fieldSeparatorLength = int(std::strlen(headerFieldSeparator)); + int assignmentIndex = headerFieldLine.indexOf(headerFieldSeparator); + if (assignmentIndex >= 0) { + return {headerFieldLine.mid(0, assignmentIndex), + headerFieldLine.mid(assignmentIndex + fieldSeparatorLength)}; + } + qCWarning(parseLog) << "Unexpected header line:" << QLatin1String(headerFieldLine); + return {}; +} + +static void parseContentType(BaseMessage &message, QByteArray contentType, QString &parseError) +{ + if (contentType.startsWith('"') && contentType.endsWith('"')) + contentType = contentType.mid(1, contentType.length() - 2); + QList<QByteArray> contentTypeElements = contentType.split(';'); + QByteArray mimeTypeName = contentTypeElements.takeFirst(); + QTextCodec *codec = nullptr; + for (const QByteArray &_contentTypeElement : contentTypeElements) { + const QByteArray &contentTypeElement = _contentTypeElement.trimmed(); + if (contentTypeElement.startsWith(contentCharsetName)) { + const int equalindex = contentTypeElement.indexOf('='); + const QByteArray charset = contentTypeElement.mid(equalindex + 1); + if (equalindex > 0) + codec = QTextCodec::codecForName(charset); + if (!codec) { + parseError = Tr::tr("Cannot decode content with \"%1\". Falling back to \"%2\".") + .arg(QLatin1String(charset), + QLatin1String(defaultCharset)); + } + } + } + message.mimeType = mimeTypeName; + message.codec = codec ? codec : BaseMessage::defaultCodec(); +} + +static void parseContentLength(BaseMessage &message, QByteArray contentLength, QString &parseError) +{ + bool ok = true; + message.contentLength = contentLength.toInt(&ok); + if (!ok) { + parseError = Tr::tr("Expected an integer in \"%1\", but got \"%2\".") + .arg(QString::fromLatin1(contentLengthFieldName), QString::fromLatin1(contentLength)); + } +} + +void BaseMessage::parse(QBuffer *data, QString &parseError, BaseMessage &message) +{ + const qint64 startPos = data->pos(); + + if (message.isValid()) { // incomplete message from last parse + message.content.append(data->read(message.contentLength - message.content.length())); + return; + } + + while (!data->atEnd()) { + const QByteArray &headerFieldLine = data->readLine(); + if (headerFieldLine == headerSeparator) { + if (message.isValid()) + message.content = data->read(message.contentLength); + return; + } + const QPair<QByteArray, QByteArray> nameAndValue = splitHeaderFieldLine(headerFieldLine); + const QByteArray &headerFieldName = nameAndValue.first.trimmed(); + const QByteArray &headerFieldValue = nameAndValue.second.trimmed(); + + if (headerFieldName.isEmpty()) + continue; + if (headerFieldName == contentLengthFieldName) { + parseContentLength(message, headerFieldValue, parseError); + } else if (headerFieldName == contentTypeFieldName) { + parseContentType(message, headerFieldValue, parseError); + } else { + qCWarning(parseLog) << "Unexpected header field" << QLatin1String(headerFieldName) + << "in" << QLatin1String(headerFieldLine); + } + } + + // the complete header wasn't received jet, waiting for the rest of it and reparse + message = BaseMessage(); + data->seek(startPos); +} + +QTextCodec *BaseMessage::defaultCodec() +{ + static QTextCodec *codec = QTextCodec::codecForName(defaultCharset); + return codec; +} + +bool BaseMessage::isComplete() const +{ + if (!isValid()) + return false; + QBS_ASSERT(content.length() <= contentLength, return true); + return content.length() == contentLength; +} + +bool BaseMessage::isValid() const +{ + return contentLength >= 0; +} + +QByteArray BaseMessage::header() const +{ + QByteArray header; + header.append(lengthHeader()); + if (codec != defaultCodec() + || (!mimeType.isEmpty() && mimeType != JsonRpcMessage::jsonRpcMimeType())) { + header.append(typeHeader()); + } + header.append(headerSeparator); + return header; +} + +QByteArray BaseMessage::lengthHeader() const +{ + return QByteArray(contentLengthFieldName) + + QByteArray(headerFieldSeparator) + + QString::number(content.size()).toLatin1() + + QByteArray(headerSeparator); +} + +QByteArray BaseMessage::typeHeader() const +{ + return QByteArray(contentTypeFieldName) + + QByteArray(headerFieldSeparator) + + mimeType + "; " + contentCharsetName + "=" + codec->name() + + QByteArray(headerSeparator); +} + +} // namespace lsp diff --git a/src/shared/lsp/basemessage.h b/src/shared/lsp/basemessage.h new file mode 100644 index 000000000..4c6f0cb48 --- /dev/null +++ b/src/shared/lsp/basemessage.h @@ -0,0 +1,48 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "languageserverprotocol_global.h" + +#include <QByteArray> +#include <QCoreApplication> +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE +class QBuffer; +class QTextCodec; +QT_END_NAMESPACE + +namespace lsp { + +LANGUAGESERVERPROTOCOL_EXPORT Q_DECLARE_LOGGING_CATEGORY(parseLog) + +class LANGUAGESERVERPROTOCOL_EXPORT BaseMessage +{ +public: + BaseMessage(); + BaseMessage(const QByteArray &mimeType, const QByteArray &content, + int expectedLength, QTextCodec *codec); + BaseMessage(const QByteArray &mimeType, const QByteArray &content); + + bool operator==(const BaseMessage &other) const; + + static void parse(QBuffer *data, QString &parseError, BaseMessage &message); + static QTextCodec *defaultCodec(); + + bool isComplete() const; + bool isValid() const; + QByteArray header() const; + + QByteArray mimeType; + QByteArray content; + int contentLength = -1; + QTextCodec *codec = defaultCodec(); + +private: + QByteArray lengthHeader() const; + QByteArray typeHeader() const; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/callhierarchy.cpp b/src/shared/lsp/callhierarchy.cpp new file mode 100644 index 000000000..af786fa95 --- /dev/null +++ b/src/shared/lsp/callhierarchy.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "callhierarchy.h" + +namespace lsp { + +bool CallHierarchyItem::isValid() const +{ + return contains(nameKey) && contains(symbolKindKey) && contains(rangeKey) && contains(uriKey) + && contains(selectionRangeKey); +} + +PrepareCallHierarchyRequest::PrepareCallHierarchyRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{} + +CallHierarchyIncomingCallsRequest::CallHierarchyIncomingCallsRequest( + const CallHierarchyCallsParams ¶ms) + : Request(methodName, params) +{} + +CallHierarchyOutgoingCallsRequest::CallHierarchyOutgoingCallsRequest( + const CallHierarchyCallsParams ¶ms) + : Request(methodName, params) +{} + +} // namespace lsp diff --git a/src/shared/lsp/callhierarchy.h b/src/shared/lsp/callhierarchy.h new file mode 100644 index 000000000..3db1f08de --- /dev/null +++ b/src/shared/lsp/callhierarchy.h @@ -0,0 +1,108 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + SymbolKind symbolKind() const { return SymbolKind(typedValue<int>(symbolKindKey)); } + void setSymbolKind(const SymbolKind &symbolKind) { insert(symbolKindKey, int(symbolKind)); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + Range selectionRange() const { return typedValue<Range>(selectionRangeKey); } + void setSelectionRange(Range selectionRange) { insert(selectionRangeKey, selectionRange); } + + std::optional<QString> detail() const { return optionalValue<QString>(detailKey); } + void setDetail(const QString &detail) { insert(detailKey, detail); } + void clearDetail() { remove(detailKey); } + + std::optional<QList<DocumentSymbol>> children() const + { return optionalArray<DocumentSymbol>(childrenKey); } + void setChildren(const QList<DocumentSymbol> &children) { insertArray(childrenKey, children); } + void clearChildren() { remove(childrenKey); } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT PrepareCallHierarchyRequest : public Request< + LanguageClientArray<CallHierarchyItem>, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit PrepareCallHierarchyRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/prepareCallHierarchy"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyCallsParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + CallHierarchyItem item() const { return typedValue<CallHierarchyItem>(itemKey); } + void setItem(const CallHierarchyItem &item) { insert(itemKey, item); } + + bool isValid() const override { return contains(itemKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyIncomingCall : public JsonObject +{ +public: + using JsonObject::JsonObject; + + CallHierarchyItem from() const { return typedValue<CallHierarchyItem>(fromKey); } + void setFrom(const CallHierarchyItem &from) { insert(fromKey, from); } + + QList<Range> fromRanges() const { return array<Range>(fromRangesKey); } + void setFromRanges(const QList<Range> &fromRanges) { insertArray(fromRangesKey, fromRanges); } + + bool isValid() const override { return contains(fromRangesKey) && contains(fromRangesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyIncomingCallsRequest : public Request< + LanguageClientArray<CallHierarchyIncomingCall>, std::nullptr_t, CallHierarchyCallsParams> +{ +public: + explicit CallHierarchyIncomingCallsRequest(const CallHierarchyCallsParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "callHierarchy/incomingCalls"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyOutgoingCall : public JsonObject +{ +public: + using JsonObject::JsonObject; + + CallHierarchyItem to() const { return typedValue<CallHierarchyItem>(toKey); } + void setTo(const CallHierarchyItem &to) { insert(toKey, to); } + + QList<Range> fromRanges() const { return array<Range>(fromRangesKey); } + void setFromRanges(const QList<Range> &fromRanges) { insertArray(fromRangesKey, fromRanges); } + + bool isValid() const override { return contains(fromRangesKey) && contains(fromRangesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyOutgoingCallsRequest : public Request< + LanguageClientArray<CallHierarchyOutgoingCall>, std::nullptr_t, CallHierarchyCallsParams> +{ +public: + explicit CallHierarchyOutgoingCallsRequest(const CallHierarchyCallsParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "callHierarchy/outgoingCalls"; +}; + +} // namespace lsp diff --git a/src/shared/lsp/client.cpp b/src/shared/lsp/client.cpp new file mode 100644 index 000000000..8479131a6 --- /dev/null +++ b/src/shared/lsp/client.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "client.h" + +namespace lsp { + +constexpr const char RegisterCapabilityRequest::methodName[]; +constexpr const char UnregisterCapabilityRequest::methodName[]; + +RegisterCapabilityRequest::RegisterCapabilityRequest(const RegistrationParams ¶ms) + : Request(methodName, params) { } + +UnregisterCapabilityRequest::UnregisterCapabilityRequest(const UnregistrationParams ¶ms) + : UnregisterCapabilityRequest(methodName, params) { } + +} // namespace lsp diff --git a/src/shared/lsp/client.h b/src/shared/lsp/client.h new file mode 100644 index 000000000..29ebc5bc6 --- /dev/null +++ b/src/shared/lsp/client.h @@ -0,0 +1,94 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +class Registration : public JsonObject +{ +public: + Registration() : Registration(QString()) {} + explicit Registration(const QString &method) + { + setId(QUuid::createUuid().toString()); + setMethod(method); + } + using JsonObject::JsonObject; + + QString id() const { return typedValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + + QString method() const { return typedValue<QString>(methodKey); } + void setMethod(const QString &method) { insert(methodKey, method); } + + QJsonValue registerOptions() const { return value(registerOptionsKey); } + void setRegisterOptions(const QJsonValue ®isterOptions) + { insert(registerOptionsKey, registerOptions); } + + bool isValid() const override { return contains(idKey) && contains(methodKey); } +}; + +class RegistrationParams : public JsonObject +{ +public: + RegistrationParams() : RegistrationParams(QList<Registration>()) {} + explicit RegistrationParams(const QList<Registration> ®istrations) + { setRegistrations(registrations); } + using JsonObject::JsonObject; + + QList<Registration> registrations() const { return array<Registration>(registrationsKey); } + void setRegistrations(const QList<Registration> ®istrations) + { insertArray(registrationsKey, registrations); } + + bool isValid() const override { return contains(registrationsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RegisterCapabilityRequest : public Request< + std::nullptr_t, std::nullptr_t, RegistrationParams> +{ +public: + explicit RegisterCapabilityRequest(const RegistrationParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "client/registerCapability"; +}; + +class Unregistration : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString id() const { return typedValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + + QString method() const { return typedValue<QString>(methodKey); } + void setMethod(const QString &method) { insert(methodKey, method); } + + bool isValid() const override { return contains(idKey) && contains(methodKey); } +}; + +class UnregistrationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QList<Unregistration> unregistrations() const + { return array<Unregistration>(unregistrationsKey); } + void setUnregistrations(const QList<Unregistration> &unregistrations) + { insertArray(unregistrationsKey, unregistrations); } + + bool isValid() const override { return contains(unregistrationsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT UnregisterCapabilityRequest : public Request< + std::nullptr_t, std::nullptr_t, UnregistrationParams> +{ +public: + explicit UnregisterCapabilityRequest(const UnregistrationParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "client/unregisterCapability"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/clientcapabilities.cpp b/src/shared/lsp/clientcapabilities.cpp new file mode 100644 index 000000000..34d9de9e7 --- /dev/null +++ b/src/shared/lsp/clientcapabilities.cpp @@ -0,0 +1,122 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clientcapabilities.h" + +namespace lsp { + +std::optional<QList<SymbolKind>> SymbolCapabilities::SymbolKindCapabilities::valueSet() const +{ + if (std::optional<QList<int>> array = optionalArray<int>(valueSetKey)) { + return std::make_optional( + Utils::transform(*array, [](int value) { return static_cast<SymbolKind>(value); })); + } + return std::nullopt; +} + +void SymbolCapabilities::SymbolKindCapabilities::setValueSet(const QList<SymbolKind> &valueSet) +{ + insert(valueSetKey, enumArrayToJsonArray<SymbolKind>(valueSet)); +} + +WorkspaceClientCapabilities::WorkspaceClientCapabilities() +{ + setWorkspaceFolders(true); +} + +std::optional<std::variant<bool, QJsonObject>> SemanticTokensClientCapabilities::Requests::range() + const +{ + using RetType = std::variant<bool, QJsonObject>; + const QJsonValue &rangeOptions = value(rangeKey); + if (rangeOptions.isBool()) + return RetType(rangeOptions.toBool()); + if (rangeOptions.isObject()) + return RetType(rangeOptions.toObject()); + return std::nullopt; +} + +void SemanticTokensClientCapabilities::Requests::setRange( + const std::variant<bool, QJsonObject> &range) +{ + insertVariant<bool, QJsonObject>(rangeKey, range); +} + +std::optional<std::variant<bool, FullSemanticTokenOptions>> +SemanticTokensClientCapabilities::Requests::full() const +{ + using RetType = std::variant<bool, FullSemanticTokenOptions>; + const QJsonValue &fullOptions = value(fullKey); + if (fullOptions.isBool()) + return RetType(fullOptions.toBool()); + if (fullOptions.isObject()) + return RetType(FullSemanticTokenOptions(fullOptions.toObject())); + return std::nullopt; +} + +void SemanticTokensClientCapabilities::Requests::setFull( + const std::variant<bool, FullSemanticTokenOptions> &full) +{ + insertVariant<bool, FullSemanticTokenOptions>(fullKey, full); +} + +std::optional<SemanticTokensClientCapabilities> TextDocumentClientCapabilities::semanticTokens() const +{ + return optionalValue<SemanticTokensClientCapabilities>(semanticTokensKey); +} + +void TextDocumentClientCapabilities::setSemanticTokens( + const SemanticTokensClientCapabilities &semanticTokens) +{ + insert(semanticTokensKey, semanticTokens); +} + +bool SemanticTokensClientCapabilities::isValid() const +{ + return contains(requestsKey) && contains(tokenTypesKey) && contains(tokenModifiersKey) + && contains(formatsKey); +} + +const char resourceOperationCreate[] = "create"; +const char resourceOperationRename[] = "rename"; +const char resourceOperationDelete[] = "delete"; + +std::optional<QList<WorkspaceClientCapabilities::WorkspaceEditCapabilities::ResourceOperationKind>> +WorkspaceClientCapabilities::WorkspaceEditCapabilities::resourceOperations() const +{ + if (!contains(resourceOperationsKey)) + return std::nullopt; + QList<ResourceOperationKind> result; + for (const QJsonValue &value : this->value(resourceOperationsKey).toArray()) { + const QString str = value.toString(); + if (str == resourceOperationCreate) + result << ResourceOperationKind::Create; + else if (str == resourceOperationRename) + result << ResourceOperationKind::Rename; + else if (str == resourceOperationDelete) + result << ResourceOperationKind::Delete; + } + return result; +} + +void WorkspaceClientCapabilities::WorkspaceEditCapabilities::setResourceOperations( + const QList<ResourceOperationKind> &resourceOperations) +{ + QJsonArray array; + for (const auto &kind : resourceOperations) { + switch (kind) { + case ResourceOperationKind::Create: + array << resourceOperationCreate; + break; + case ResourceOperationKind::Rename: + array << resourceOperationRename; + break; + case ResourceOperationKind::Delete: + array << resourceOperationDelete; + break; + } + } + insert(resourceOperationsKey, array); +} + +} // namespace lsp diff --git a/src/shared/lsp/clientcapabilities.h b/src/shared/lsp/clientcapabilities.h new file mode 100644 index 000000000..44add0a2c --- /dev/null +++ b/src/shared/lsp/clientcapabilities.h @@ -0,0 +1,674 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonkeys.h" +#include "lsptypes.h" +#include "semantictokens.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT DynamicRegistrationCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<bool> dynamicRegistration() const + { + return optionalValue<bool>(dynamicRegistrationKey); + } + void setDynamicRegistration(bool dynamicRegistration) { insert(dynamicRegistrationKey, dynamicRegistration); } + void clearDynamicRegistration() { remove(dynamicRegistrationKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT FullSemanticTokenOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * The client will send the `textDocument/semanticTokens/full/delta` + * request if the server provides a corresponding handler. + */ + std::optional<bool> delta() const { return optionalValue<bool>(deltaKey); } + void setDelta(bool delta) { insert(deltaKey, delta); } + void clearDelta() { remove(deltaKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensClientCapabilities : public DynamicRegistrationCapabilities +{ +public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + class LANGUAGESERVERPROTOCOL_EXPORT Requests : public JsonObject + { + /** + * Which requests the client supports and might send to the server + * depending on the server's capability. Please note that clients might not + * show semantic tokens or degrade some of the user experience if a range + * or full request is advertised by the client but not provided by the + * server. If for example the client capability `requests.full` and + * `request.range` are both set to true but the server only provides a + * range provider the client might not render a minimap correctly or might + * even decide to not show any semantic tokens at all. + */ + public: + using JsonObject::JsonObject; + + /** + * The client will send the `textDocument/semanticTokens/range` request + * if the server provides a corresponding handler. + */ + std::optional<std::variant<bool, QJsonObject>> range() const; + void setRange(const std::variant<bool, QJsonObject> &range); + void clearRange() { remove(rangeKey); } + + /** + * The client will send the `textDocument/semanticTokens/full` request + * if the server provides a corresponding handler. + */ + std::optional<std::variant<bool, FullSemanticTokenOptions>> full() const; + void setFull(const std::variant<bool, FullSemanticTokenOptions> &full); + void clearFull() { remove(fullKey); } + }; + + Requests requests() const { return typedValue<Requests>(requestsKey); } + void setRequests(const Requests &requests) { insert(requestsKey, requests); } + + /// The token types that the client supports. + QList<QString> tokenTypes() const { return array<QString>(tokenTypesKey); } + void setTokenTypes(const QList<QString> &value) { insertArray(tokenTypesKey, value); } + + /// The token modifiers that the client supports. + QList<QString> tokenModifiers() const { return array<QString>(tokenModifiersKey); } + void setTokenModifiers(const QList<QString> &value) { insertArray(tokenModifiersKey, value); } + + /// The formats the clients supports. + QList<QString> formats() const { return array<QString>(formatsKey); } + void setFormats(const QList<QString> &value) { insertArray(formatsKey, value); } + + /// Whether the client supports tokens that can overlap each other. + std::optional<bool> overlappingTokenSupport() const + { + return optionalValue<bool>(overlappingTokenSupportKey); + } + void setOverlappingTokenSupport(bool overlappingTokenSupport) { insert(overlappingTokenSupportKey, overlappingTokenSupport); } + void clearOverlappingTokenSupport() { remove(overlappingTokenSupportKey); } + + /// Whether the client supports tokens that can span multiple lines. + std::optional<bool> multiLineTokenSupport() const + { + return optionalValue<bool>(multiLineTokenSupportKey); + } + void setMultiLineTokenSupport(bool multiLineTokenSupport) { insert(multiLineTokenSupportKey, multiLineTokenSupport); } + void clearMultiLineTokenSupport() { remove(multiLineTokenSupportKey); } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SymbolCapabilities : public DynamicRegistrationCapabilities +{ +public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class LANGUAGESERVERPROTOCOL_EXPORT SymbolKindCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + /* + * The symbol kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + * + * If this property is not present the client only supports + * the symbol kinds from `File` to `Array` as defined in + * the initial version of the protocol. + */ + std::optional<QList<SymbolKind>> valueSet() const; + void setValueSet(const QList<SymbolKind> &valueSet); + void clearValueSet() { remove(valueSetKey); } + }; + + // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + std::optional<SymbolKindCapabilities> symbolKind() const + { return optionalValue<SymbolKindCapabilities>(symbolKindKey); } + void setSymbolKind(const SymbolKindCapabilities &symbolKind) { insert(symbolKindKey, symbolKind); } + void clearSymbolKind() { remove(symbolKindKey); } + + std::optional<bool> hierarchicalDocumentSymbolSupport() const + { return optionalValue<bool>(hierarchicalDocumentSymbolSupportKey); } + void setHierarchicalDocumentSymbolSupport(bool hierarchicalDocumentSymbolSupport) + { insert(hierarchicalDocumentSymbolSupportKey, hierarchicalDocumentSymbolSupport); } + void clearHierachicalDocumentSymbolSupport() { remove(hierarchicalDocumentSymbolSupportKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentClientCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT SynchronizationCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + // The client supports sending will save notifications. + std::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); } + void setWillSave(bool willSave) { insert(willSaveKey, willSave); } + void clearWillSave() { remove(willSaveKey); } + + /* + * The client supports sending a will save request and + * waits for a response providing text edits which will + * be applied to the document before it is saved. + */ + std::optional<bool> willSaveWaitUntil() const + { return optionalValue<bool>(willSaveWaitUntilKey); } + void setWillSaveWaitUntil(bool willSaveWaitUntil) + { insert(willSaveWaitUntilKey, willSaveWaitUntil); } + void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); } + + // The client supports did save notifications. + std::optional<bool> didSave() const { return optionalValue<bool>(didSaveKey); } + void setDidSave(bool didSave) { insert(didSaveKey, didSave); } + void clearDidSave() { remove(didSaveKey); } + }; + + std::optional<SynchronizationCapabilities> synchronization() const + { return optionalValue<SynchronizationCapabilities>(synchronizationKey); } + void setSynchronization(const SynchronizationCapabilities &synchronization) + { insert(synchronizationKey, synchronization); } + void clearSynchronization() { remove(synchronizationKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT CompletionCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemCapbilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + /* + * Client supports snippets as insert text. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + std::optional<bool> snippetSupport() const + { return optionalValue<bool>(snippetSupportKey); } + void setSnippetSupport(bool snippetSupport) + { insert(snippetSupportKey, snippetSupport); } + void clearSnippetSupport() { remove(snippetSupportKey); } + + // Client supports commit characters on a completion item. + std::optional<bool> commitCharacterSupport() const + { return optionalValue<bool>(commitCharacterSupportKey); } + void setCommitCharacterSupport(bool commitCharacterSupport) + { insert(commitCharacterSupportKey, commitCharacterSupport); } + void clearCommitCharacterSupport() { remove(commitCharacterSupportKey); } + + /* + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + std::optional<QList<MarkupKind>> documentationFormat() const; + void setDocumentationFormat(const QList<MarkupKind> &documentationFormat); + void clearDocumentationFormat() { remove(documentationFormatKey); } + }; + + // The client supports the following `CompletionItem` specific capabilities. + std::optional<CompletionItemCapbilities> completionItem() const + { return optionalValue<CompletionItemCapbilities>(completionItemKey); } + void setCompletionItem(const CompletionItemCapbilities &completionItem) + { insert(completionItemKey, completionItem); } + void clearCompletionItem() { remove(completionItemKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemKindCapabilities : public JsonObject + { + public: + CompletionItemKindCapabilities(); + using JsonObject::JsonObject; + /* + * The completion item kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + * + * If this property is not present the client only supports + * the completion items kinds from `Text` to `Reference` as defined in + * the initial version of the protocol. + */ + std::optional<QList<CompletionItemKind::Kind>> valueSet() const; + void setValueSet(const QList<CompletionItemKind::Kind> &valueSet); + void clearValueSet() { remove(valueSetKey); } + }; + + std::optional<CompletionItemKindCapabilities> completionItemKind() const + { return optionalValue<CompletionItemKindCapabilities>(completionItemKindKey); } + void setCompletionItemKind(const CompletionItemKindCapabilities &completionItemKind) + { insert(completionItemKindKey, completionItemKind); } + void clearCompletionItemKind() { remove(completionItemKindKey); } + + /* + * The client supports to send additional context information for a + * `textDocument/completion` request. + */ + std::optional<bool> contextSupport() const + { + return optionalValue<bool>(contextSupportKey); + } + void setContextSupport(bool contextSupport) { insert(contextSupportKey, contextSupport); } + void clearContextSupport() { remove(contextSupportKey); } + }; + + // Capabilities specific to the `textDocument/completion` + std::optional<CompletionCapabilities> completion() const + { return optionalValue<CompletionCapabilities>(completionKey); } + void setCompletion(const CompletionCapabilities &completion) + { insert(completionKey, completion); } + void clearCompletion() { remove(completionKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT HoverCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + /* + * Client supports the follow content formats for the content + * property. The order describes the preferred format of the client. + */ + std::optional<QList<MarkupKind>> contentFormat() const; + void setContentFormat(const QList<MarkupKind> &contentFormat); + void clearContentFormat() { remove(contentFormatKey); } + }; + + std::optional<HoverCapabilities> hover() const + { + return optionalValue<HoverCapabilities>(hoverKey); + } + void setHover(const HoverCapabilities &hover) { insert(hoverKey, hover); } + void clearHover() { remove(hoverKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class LANGUAGESERVERPROTOCOL_EXPORT SignatureInformationCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + /* + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + std::optional<QList<MarkupKind>> documentationFormat() const; + void setDocumentationFormat(const QList<MarkupKind> &documentationFormat); + void clearDocumentationFormat() { remove(documentationFormatKey); } + + std::optional<bool> activeParameterSupport() const + { return optionalValue<bool>(activeParameterSupportKey); } + void setActiveParameterSupport(bool activeParameterSupport) + { insert(activeParameterSupportKey, activeParameterSupport); } + void clearActiveParameterSupport() { remove(activeParameterSupportKey); } + }; + + // The client supports the following `SignatureInformation` specific properties. + std::optional<SignatureInformationCapabilities> signatureInformation() const + { return optionalValue<SignatureInformationCapabilities>(signatureInformationKey); } + void setSignatureInformation(const SignatureInformationCapabilities &signatureInformation) + { insert(signatureInformationKey, signatureInformation); } + void clearSignatureInformation() { remove(signatureInformationKey); } + }; + + // Capabilities specific to the `textDocument/signatureHelp` + std::optional<SignatureHelpCapabilities> signatureHelp() const + { return optionalValue<SignatureHelpCapabilities>(signatureHelpKey); } + void setSignatureHelp(const SignatureHelpCapabilities &signatureHelp) + { insert(signatureHelpKey, signatureHelp); } + void clearSignatureHelp() { remove(signatureHelpKey); } + + // Whether references supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> references() const + { return optionalValue<DynamicRegistrationCapabilities>(referencesKey); } + void setReferences(const DynamicRegistrationCapabilities &references) + { insert(referencesKey, references); } + void clearReferences() { remove(referencesKey); } + + // Whether document highlight supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> documentHighlight() const + { return optionalValue<DynamicRegistrationCapabilities>(documentHighlightKey); } + void setDocumentHighlight(const DynamicRegistrationCapabilities &documentHighlight) + { insert(documentHighlightKey, documentHighlight); } + void clearDocumentHighlight() { remove(documentHighlightKey); } + + // Capabilities specific to the `textDocument/documentSymbol` + std::optional<SymbolCapabilities> documentSymbol() const + { return optionalValue<SymbolCapabilities>(documentSymbolKey); } + void setDocumentSymbol(const SymbolCapabilities &documentSymbol) + { insert(documentSymbolKey, documentSymbol); } + void clearDocumentSymbol() { remove(documentSymbolKey); } + + // Whether formatting supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> formatting() const + { return optionalValue<DynamicRegistrationCapabilities>(formattingKey); } + void setFormatting(const DynamicRegistrationCapabilities &formatting) + { insert(formattingKey, formatting); } + void clearFormatting() { remove(formattingKey); } + + // Whether range formatting supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> rangeFormatting() const + { return optionalValue<DynamicRegistrationCapabilities>(rangeFormattingKey); } + void setRangeFormatting(const DynamicRegistrationCapabilities &rangeFormatting) + { insert(rangeFormattingKey, rangeFormatting); } + void clearRangeFormatting() { remove(rangeFormattingKey); } + + // Whether on type formatting supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> onTypeFormatting() const + { return optionalValue<DynamicRegistrationCapabilities>(onTypeFormattingKey); } + void setOnTypeFormatting(const DynamicRegistrationCapabilities &onTypeFormatting) + { insert(onTypeFormattingKey, onTypeFormatting); } + void clearOnTypeFormatting() { remove(onTypeFormattingKey); } + + // Whether definition supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> definition() const + { return optionalValue<DynamicRegistrationCapabilities>(definitionKey); } + void setDefinition(const DynamicRegistrationCapabilities &definition) + { insert(definitionKey, definition); } + void clearDefinition() { remove(definitionKey); } + + /* + * Whether typeDefinition supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + std::optional<DynamicRegistrationCapabilities> typeDefinition() const + { return optionalValue<DynamicRegistrationCapabilities>(typeDefinitionKey); } + void setTypeDefinition(const DynamicRegistrationCapabilities &typeDefinition) + { insert(typeDefinitionKey, typeDefinition); } + void clearTypeDefinition() { remove(typeDefinitionKey); } + + /* + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + std::optional<DynamicRegistrationCapabilities> implementation() const + { return optionalValue<DynamicRegistrationCapabilities>(implementationKey); } + void setImplementation(const DynamicRegistrationCapabilities &implementation) + { insert(implementationKey, implementation); } + void clearImplementation() { remove(implementationKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT CodeActionCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class LANGUAGESERVERPROTOCOL_EXPORT CodeActionLiteralSupport : public JsonObject + { + public: + using JsonObject::JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT CodeActionKind : public JsonObject + { + public: + using JsonObject::JsonObject; + CodeActionKind() : CodeActionKind(QList<QString>()) {} + explicit CodeActionKind(const QList<QString> &kinds) { setValueSet(kinds); } + + QList<QString> valueSet() const { return array<QString>(valueSetKey); } + void setValueSet(const QList<QString> &valueSet) + { insertArray(valueSetKey, valueSet); } + + bool isValid() const override { return contains(valueSetKey); } + }; + + CodeActionKind codeActionKind() const + { return typedValue<CodeActionKind>(codeActionKindKey); } + void setCodeActionKind(const CodeActionKind &codeActionKind) + { insert(codeActionKindKey, codeActionKind); } + + bool isValid() const override { return contains(codeActionKindKey); } + }; + + std::optional<CodeActionLiteralSupport> codeActionLiteralSupport() const + { return optionalValue<CodeActionLiteralSupport>(codeActionLiteralSupportKey); } + void setCodeActionLiteralSupport(const CodeActionLiteralSupport &codeActionLiteralSupport) + { insert(codeActionLiteralSupportKey, codeActionLiteralSupport); } + void clearCodeActionLiteralSupport() { remove(codeActionLiteralSupportKey); } + }; + + // Whether code action supports dynamic registration. + std::optional<CodeActionCapabilities> codeAction() const + { return optionalValue<CodeActionCapabilities>(codeActionKey); } + void setCodeAction(const CodeActionCapabilities &codeAction) + { insert(codeActionKey, codeAction); } + void clearCodeAction() { remove(codeActionKey); } + + // Whether code lens supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> codeLens() const + { return optionalValue<DynamicRegistrationCapabilities>(codeLensKey); } + void setCodeLens(const DynamicRegistrationCapabilities &codeLens) + { insert(codeLensKey, codeLens); } + void clearCodeLens() { remove(codeLensKey); } + + // Whether document link supports dynamic registration. + std::optional<DynamicRegistrationCapabilities> documentLink() const + { return optionalValue<DynamicRegistrationCapabilities>(documentLinkKey); } + void setDocumentLink(const DynamicRegistrationCapabilities &documentLink) + { insert(documentLinkKey, documentLink); } + void clearDocumentLink() { remove(documentLinkKey); } + + /* + * Whether colorProvider supports dynamic registration. If this is set to `true` + * the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + std::optional<DynamicRegistrationCapabilities> colorProvider() const + { return optionalValue<DynamicRegistrationCapabilities>(colorProviderKey); } + void setColorProvider(const DynamicRegistrationCapabilities &colorProvider) + { insert(colorProviderKey, colorProvider); } + void clearColorProvider() { remove(colorProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT RenameClientCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + /** + * Client supports testing for validity of rename operations + * before execution. + * + * @since version 3.12.0 + */ + + std::optional<bool> prepareSupport() const + { + return optionalValue<bool>(prepareSupportKey); + } + void setPrepareSupport(bool prepareSupport) { insert(prepareSupportKey, prepareSupport); } + void clearPrepareSupport() { remove(prepareSupportKey); } + }; + + // Whether rename supports dynamic registration. + std::optional<RenameClientCapabilities> rename() const + { return optionalValue<RenameClientCapabilities>(renameKey); } + void setRename(const RenameClientCapabilities &rename) + { insert(renameKey, rename); } + void clearRename() { remove(renameKey); } + + std::optional<SemanticTokensClientCapabilities> semanticTokens() const; + void setSemanticTokens(const SemanticTokensClientCapabilities &semanticTokens); + void clearSemanticTokens() { remove(semanticTokensKey); } + + std::optional<DynamicRegistrationCapabilities> callHierarchy() const + { return optionalValue<DynamicRegistrationCapabilities>(callHierarchyKey); } + void setCallHierarchy(const DynamicRegistrationCapabilities &callHierarchy) + { insert(callHierarchyKey, callHierarchy); } + void clearCallHierarchy() { remove(callHierarchyKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensWorkspaceClientCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + /** + * Whether the client implementation supports a refresh request sent from + * the server to the client. + * + * Note that this event is global and will force the client to refresh all + * semantic tokens currently shown. It should be used with absolute care + * and is useful for situation where a server for example detect a project + * wide change that requires such a calculation. + */ + std::optional<bool> refreshSupport() const { return optionalValue<bool>(refreshSupportKey); } + void setRefreshSupport(bool refreshSupport) { insert(refreshSupportKey, refreshSupport); } + void clearRefreshSupport() { remove(refreshSupportKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceClientCapabilities : public JsonObject +{ +public: + WorkspaceClientCapabilities(); + using JsonObject::JsonObject; + + /* + * The client supports applying batch edits to the workspace by supporting the request + * 'workspace/applyEdit' + */ + std::optional<bool> applyEdit() const { return optionalValue<bool>(applyEditKey); } + void setApplyEdit(bool applyEdit) { insert(applyEditKey, applyEdit); } + void clearApplyEdit() { remove(applyEditKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEditCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The client supports versioned document changes in `WorkspaceEdit`s + std::optional<bool> documentChanges() const + { return optionalValue<bool>(documentChangesKey); } + void setDocumentChanges(bool documentChanges) + { insert(documentChangesKey, documentChanges); } + void clearDocumentChanges() { remove(documentChangesKey); } + + enum class ResourceOperationKind { Create, Rename, Delete }; + + // The resource operations the client supports. Clients should at least support 'create', + // 'rename' and 'delete' files and folders. + std::optional<QList<ResourceOperationKind>> resourceOperations() const; + void setResourceOperations(const QList<ResourceOperationKind> &resourceOperations); + void clearResourceOperations() { remove(resourceOperationsKey); } + }; + + // Capabilities specific to `WorkspaceEdit`s + std::optional<WorkspaceEditCapabilities> workspaceEdit() const + { return optionalValue<WorkspaceEditCapabilities>(workspaceEditKey); } + void setWorkspaceEdit(const WorkspaceEditCapabilities &workspaceEdit) + { insert(workspaceEditKey, workspaceEdit); } + void clearWorkspaceEdit() { remove(workspaceEditKey); } + + // Capabilities specific to the `workspace/didChangeConfiguration` notification. + std::optional<DynamicRegistrationCapabilities> didChangeConfiguration() const + { return optionalValue<DynamicRegistrationCapabilities>(didChangeConfigurationKey); } + void setDidChangeConfiguration(const DynamicRegistrationCapabilities &didChangeConfiguration) + { insert(didChangeConfigurationKey, didChangeConfiguration); } + void clearDidChangeConfiguration() { remove(didChangeConfigurationKey); } + + // Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + std::optional<DynamicRegistrationCapabilities> didChangeWatchedFiles() const + { return optionalValue<DynamicRegistrationCapabilities>(didChangeWatchedFilesKey); } + void setDidChangeWatchedFiles(const DynamicRegistrationCapabilities &didChangeWatchedFiles) + { insert(didChangeWatchedFilesKey, didChangeWatchedFiles); } + void clearDidChangeWatchedFiles() { remove(didChangeWatchedFilesKey); } + + // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + std::optional<SymbolCapabilities> symbol() const + { return optionalValue<SymbolCapabilities>(symbolKey); } + void setSymbol(const SymbolCapabilities &symbol) { insert(symbolKey, symbol); } + void clearSymbol() { remove(symbolKey); } + + // Capabilities specific to the `workspace/executeCommand` request. + std::optional<DynamicRegistrationCapabilities> executeCommand() const + { return optionalValue<DynamicRegistrationCapabilities>(executeCommandKey); } + void setExecuteCommand(const DynamicRegistrationCapabilities &executeCommand) + { insert(executeCommandKey, executeCommand); } + void clearExecuteCommand() { remove(executeCommandKey); } + + // The client has support for workspace folders. Since 3.6.0 + std::optional<bool> workspaceFolders() const + { return optionalValue<bool>(workspaceFoldersKey); } + void setWorkspaceFolders(bool workspaceFolders) + { insert(workspaceFoldersKey, workspaceFolders); } + void clearWorkspaceFolders() { remove(workspaceFoldersKey); } + + // The client supports `workspace/configuration` requests. Since 3.6.0 + std::optional<bool> configuration() const { return optionalValue<bool>(configurationKey); } + void setConfiguration(bool configuration) { insert(configurationKey, configuration); } + void clearConfiguration() { remove(configurationKey); } + + std::optional<SemanticTokensWorkspaceClientCapabilities> semanticTokens() const + { return optionalValue<SemanticTokensWorkspaceClientCapabilities>(semanticTokensKey); } + void setSemanticTokens(const SemanticTokensWorkspaceClientCapabilities &semanticTokens) + { insert(semanticTokensKey, semanticTokens); } + void clearSemanticTokens() { remove(semanticTokensKey); } +}; + +class WindowClientClientCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * Whether client supports handling progress notifications. + * If set, servers are allowed to report in `workDoneProgress` property + * in the request specific server capabilities. + * + */ + std::optional<bool> workDoneProgress() const + { return optionalValue<bool>(workDoneProgressKey); } + void setWorkDoneProgress(bool workDoneProgress) + { insert(workDoneProgressKey, workDoneProgress); } + void clearWorkDoneProgress() { remove(workDoneProgressKey); } + +private: + constexpr static const char workDoneProgressKey[] = "workDoneProgress"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ClientCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Workspace specific client capabilities. + std::optional<WorkspaceClientCapabilities> workspace() const + { return optionalValue<WorkspaceClientCapabilities>(workspaceKey); } + void setWorkspace(const WorkspaceClientCapabilities &workspace) + { insert(workspaceKey, workspace); } + void clearWorkspace() { remove(workspaceKey); } + + // Text document specific client capabilities. + std::optional<TextDocumentClientCapabilities> textDocument() const + { return optionalValue<TextDocumentClientCapabilities>(textDocumentKey); } + void setTextDocument(const TextDocumentClientCapabilities &textDocument) + { insert(textDocumentKey, textDocument); } + void clearTextDocument() { remove(textDocumentKey); } + + // Window specific client capabilities. + std::optional<WindowClientClientCapabilities> window() const + { return optionalValue<WindowClientClientCapabilities>(windowKey); } + void setWindow(const WindowClientClientCapabilities &window) + { insert(windowKey, window); } + void clearWindow() { remove(windowKey); } + + // Experimental client capabilities. + QJsonValue experimental() const { return value(experimentalKey); } + void setExperimental(const QJsonValue &experimental) { insert(experimentalKey, experimental); } + void clearExperimental() { remove(experimentalKey); } +}; + +} // namespace lsp diff --git a/src/shared/lsp/completion.cpp b/src/shared/lsp/completion.cpp new file mode 100644 index 000000000..48a2ac868 --- /dev/null +++ b/src/shared/lsp/completion.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "completion.h" + +namespace lsp { + +constexpr const char CompletionRequest::methodName[]; +constexpr const char CompletionItemResolveRequest::methodName[]; + +CompletionRequest::CompletionRequest(const CompletionParams ¶ms) + : Request(methodName, params) +{ } + +std::optional<MarkupOrString> CompletionItem::documentation() const +{ + QJsonValue documentation = value(documentationKey); + if (documentation.isUndefined()) + return std::nullopt; + return MarkupOrString(documentation); +} + +std::optional<CompletionItem::InsertTextFormat> CompletionItem::insertTextFormat() const +{ + if (std::optional<int> value = optionalValue<int>(insertTextFormatKey)) + return std::make_optional(CompletionItem::InsertTextFormat(*value)); + return std::nullopt; +} + +std::optional<QList<CompletionItem::CompletionItemTag>> CompletionItem::tags() const +{ + if (const auto value = optionalValue<QJsonArray>(tagsKey)) { + QList<CompletionItemTag> tags; + for (auto it = value->cbegin(); it != value->cend(); ++it) + tags << static_cast<CompletionItemTag>(it->toInt()); + return tags; + } + return {}; +} + +CompletionItemResolveRequest::CompletionItemResolveRequest(const CompletionItem ¶ms) + : Request(methodName, params) +{ } + +CompletionResult::CompletionResult(const QJsonValue &value) +{ + if (value.isNull()) { + emplace<std::nullptr_t>(nullptr); + } else if (value.isArray()) { + QList<CompletionItem> items; + for (auto arrayElement : value.toArray()) + items << CompletionItem(arrayElement); + emplace<QList<CompletionItem>>(items); + } else if (value.isObject()) { + emplace<CompletionList>(CompletionList(value)); + } +} + +} // namespace lsp diff --git a/src/shared/lsp/completion.h b/src/shared/lsp/completion.h new file mode 100644 index 000000000..665b5a5ae --- /dev/null +++ b/src/shared/lsp/completion.h @@ -0,0 +1,269 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" +#include "languagefeatures.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionParams : public TextDocumentPositionParams +{ +public: + using TextDocumentPositionParams::TextDocumentPositionParams; + + enum CompletionTriggerKind { + /** + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ + Invoked = 1, + /** + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ + TriggerCharacter = 2, + /// Completion was re-triggered as the current completion list is incomplete. + TriggerForIncompleteCompletions = 3 + }; + + class CompletionContext : public JsonObject + { + public: + using JsonObject::JsonObject; + + /// How the completion was triggered. + CompletionTriggerKind triggerKind() const + { return CompletionTriggerKind(typedValue<int>(triggerKindKey)); } + void setTriggerKind(CompletionTriggerKind triggerKind) + { insert(triggerKindKey, triggerKind); } + + /** + * The trigger character (a single character) that has trigger code complete. + * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + */ + std::optional<QString> triggerCharacter() const + { return optionalValue<QString>(triggerCharacterKey); } + void setTriggerCharacter(const QString &triggerCharacter) + { insert(triggerCharacterKey, triggerCharacter); } + void clearTriggerCharacter() { remove(triggerCharacterKey); } + + bool isValid() const override { return contains(triggerKindKey); } + }; + + /** + * The completion context. This is only available it the client specifies + * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` + */ + std::optional<CompletionContext> context() const + { return optionalValue<CompletionContext>(contextKey); } + void setContext(const CompletionContext &context) + { insert(contextKey, context); } + void clearContext() { remove(contextKey); } + + // clangd extension + void setLimit(int limit) { insert(limitKey, limit); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * The label of this completion item. By default also the text that is inserted when selecting + * this completion. + */ + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + /// The kind of this completion item. Based of the kind an icon is chosen by the editor. + std::optional<int> kind() const { return optionalValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + void clearKind() { remove(kindKey); } + + /// A human-readable string with additional information about this item, like type information. + std::optional<QString> detail() const { return optionalValue<QString>(detailKey); } + void setDetail(const QString &detail) { insert(detailKey, detail); } + void clearDetail() { remove(detailKey); } + + /// A human-readable string that represents a doc-comment. + std::optional<MarkupOrString> documentation() const; + void setDocumentation(const MarkupOrString &documentation) + { insert(documentationKey, documentation.toJson()); } + void cleatDocumentation() { remove(documentationKey); } + + /// A string that should be used when comparing this item + /// with other items. When `falsy` the label is used. + std::optional<QString> sortText() const { return optionalValue<QString>(sortTextKey); } + void setSortText(const QString &sortText) { insert(sortTextKey, sortText); } + void clearSortText() { remove(sortTextKey); } + + /// A string that should be used when filtering a set of + /// completion items. When `falsy` the label is used. + std::optional<QString> filterText() const { return optionalValue<QString>(filterTextKey); } + void setFilterText(const QString &filterText) { insert(filterTextKey, filterText); } + void clearFilterText() { remove(filterTextKey); } + + /** + * A string that should be inserted into a document when selecting + * this completion. When `falsy` the label is used. + * + * The `insertText` is subject to interpretation by the client side. + * Some tools might not take the string literally. For example + * VS Code when code complete is requested in this example `con<cursor position>` + * and a completion item with an `insertText` of `console` is provided it + * will only insert `sole`. Therefore it is recommended to use `textEdit` instead + * since it avoids additional client side interpretation. + * + * @deprecated Use textEdit instead. + */ + std::optional<QString> insertText() const { return optionalValue<QString>(insertTextKey); } + void setInsertText(const QString &insertText) { insert(insertTextKey, insertText); } + void clearInsertText() { remove(insertTextKey); } + + enum InsertTextFormat { + /// The primary text to be inserted is treated as a plain string. + PlainText = 1, + + /** + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + Snippet = 2 + }; + + /// The format of the insert text. The format applies to both the `insertText` property + /// and the `newText` property of a provided `textEdit`. + std::optional<InsertTextFormat> insertTextFormat() const; + void setInsertTextFormat(const InsertTextFormat &insertTextFormat) + { insert(insertTextFormatKey, insertTextFormat); } + void clearInsertTextFormat() { remove(insertTextFormatKey); } + + /** + * An edit which is applied to a document when selecting this completion. When an edit is provided the value of + * `insertText` is ignored. + * + * *Note:* The range of the edit must be a single line range and it must contain the position at which completion + * has been requested. + */ + std::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); } + void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); } + void clearTextEdit() { remove(textEditKey); } + + /** + * An optional array of additional text edits that are applied when + * selecting this completion. Edits must not overlap with the main edit + * nor with themselves. + */ + std::optional<QList<TextEdit>> additionalTextEdits() const + { return optionalArray<TextEdit>(additionalTextEditsKey); } + void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits) + { insertArray(additionalTextEditsKey, additionalTextEdits); } + void clearAdditionalTextEdits() { remove(additionalTextEditsKey); } + + /** + * An optional set of characters that when pressed while this completion is active will accept it first and + * then type that character. *Note* that all commit characters should have `length=1` and that superfluous + * characters will be ignored. + */ + std::optional<QList<QString>> commitCharacters() const + { return optionalArray<QString>(commitCharactersKey); } + void setCommitCharacters(const QList<QString> &commitCharacters) + { insertArray(commitCharactersKey, commitCharacters); } + void clearCommitCharacters() { remove(commitCharactersKey); } + + /** + * An optional command that is executed *after* inserting this completion. *Note* that + * additional modifications to the current document should be described with the + * additionalTextEdits-property. + */ + std::optional<Command> command() const { return optionalValue<Command>(commandKey); } + void setCommand(const Command &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + /** + * An data entry field that is preserved on a completion item between + * a completion and a completion resolve request. + */ + std::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); } + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + /** + * Completion item tags are extra annotations that tweak the rendering of a + * completion item. + * @since 3.15.0 + */ + enum CompletionItemTag { + Deprecated = 1, + }; + + /** + * Tags for this completion item. + * @since 3.15.0 + */ + std::optional<QList<CompletionItemTag>> tags() const; + + /** + * Indicates if this item is deprecated. + * @deprecated Use `tags` instead if supported. + */ + std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); } + + bool isValid() const override { return contains(labelKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionList : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * This list it not complete. Further typing should result in recomputing + * this list. + */ + bool isIncomplete() const { return typedValue<bool>(isIncompleteKey); } + void setIsIncomplete(bool isIncomplete) { insert(isIncompleteKey, isIncomplete); } + + /// The completion items. + std::optional<QList<CompletionItem>> items() const { return array<CompletionItem>(itemsKey); } + void setItems(const QList<CompletionItem> &items) { insertArray(itemsKey, items); } + void clearItems() { remove(itemsKey); } + + bool isValid() const override { return contains(isIncompleteKey); } +}; + +/// The result of a completion is CompletionItem[] | CompletionList | null +class LANGUAGESERVERPROTOCOL_EXPORT CompletionResult + : public std::variant<QList<CompletionItem>, CompletionList, std::nullptr_t> +{ +public: + using variant::variant; + explicit CompletionResult(const QJsonValue &value); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionRequest : public Request< + CompletionResult, std::nullptr_t, CompletionParams> +{ +public: + explicit CompletionRequest(const CompletionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/completion"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemResolveRequest : public Request< + CompletionItem, std::nullptr_t, CompletionItem> +{ +public: + explicit CompletionItemResolveRequest(const CompletionItem ¶ms); + using Request::Request; + constexpr static const char methodName[] = "completionItem/resolve"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/diagnostics.cpp b/src/shared/lsp/diagnostics.cpp new file mode 100644 index 000000000..352b1572f --- /dev/null +++ b/src/shared/lsp/diagnostics.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "diagnostics.h" + +namespace lsp { + +constexpr const char PublishDiagnosticsNotification::methodName[]; + +PublishDiagnosticsNotification::PublishDiagnosticsNotification(const PublishDiagnosticsParams ¶ms) + : Notification(methodName, params) { } + +} // namespace lsp diff --git a/src/shared/lsp/diagnostics.h b/src/shared/lsp/diagnostics.h new file mode 100644 index 000000000..ae479a23b --- /dev/null +++ b/src/shared/lsp/diagnostics.h @@ -0,0 +1,38 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" +#include "languagefeatures.h" + +namespace lsp { + +class PublishDiagnosticsParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); } + void setDiagnostics(const QList<Diagnostic> &diagnostics) + { insertArray(diagnosticsKey, diagnostics); } + + std::optional<int> version() const { return optionalValue<int>(versionKey); } + void setVersion(int version) { insert(versionKey, version); } + void clearVersion() { remove(versionKey); } + + bool isValid() const override { return contains(diagnosticsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT PublishDiagnosticsNotification : public Notification<PublishDiagnosticsParams> +{ +public: + explicit PublishDiagnosticsNotification(const PublishDiagnosticsParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/publishDiagnostics"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/filepath.h b/src/shared/lsp/filepath.h new file mode 100644 index 000000000..736085828 --- /dev/null +++ b/src/shared/lsp/filepath.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <tools/hostosinfo.h> + +#include <QDir> +#include <QFileInfo> +#include <QString> + +namespace lsp::Utils { + +class FilePath : public QString +{ +public: + FilePath() = default; + FilePath(const QString &s) : QString(s) {} + + QString scheme() const { return {}; } + QString host() const { return {}; } + Qt::CaseSensitivity caseSensitivity() const { + return qbs::Internal::HostOsInfo::fileNameCaseSensitivity(); + } + QString toString() const { return *this; } + QString path() const { return toString(); } + + static FilePath fromUserInput(const QString &s) { return QDir::fromNativeSeparators(s); } + QString toUserOutput() const { return QDir::toNativeSeparators(*this); } +}; + +} // namespace lsp::Utils diff --git a/src/shared/lsp/initializemessages.cpp b/src/shared/lsp/initializemessages.cpp new file mode 100644 index 000000000..db0abcf49 --- /dev/null +++ b/src/shared/lsp/initializemessages.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "initializemessages.h" + +#include <QCoreApplication> + +namespace lsp { + +constexpr const char InitializeRequest::methodName[]; +constexpr const char InitializeNotification::methodName[]; +constexpr Trace::Values s_trace = Trace::off; + +Trace Trace::fromString(const QString &val) +{ + if (val == "messages") + return messages; + if (val == "verbose") + return verbose; + return off; +} + +#define RETURN_CASE(name) case Trace::name: return QString(#name); +QString Trace::toString() const +{ + switch (m_value) { + RETURN_CASE(off); + RETURN_CASE(messages); + RETURN_CASE(verbose); + } + return QString("off"); +} +#undef RETURN_CASE + +std::optional<QList<MarkupKind>> +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities:: +documentationFormat() const +{ + return optionalArray<MarkupKind>(documentationFormatKey); +} + +void +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities:: +setDocumentationFormat(const QList<MarkupKind> &documentationFormat) +{ + insertArray(documentationFormatKey, documentationFormat); +} + +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::CompletionItemKindCapabilities() +{ + setValueSet({CompletionItemKind::Text, CompletionItemKind::Method, CompletionItemKind::Function, + CompletionItemKind::Constructor, CompletionItemKind::Field, CompletionItemKind::Variable, + CompletionItemKind::Class, CompletionItemKind::Interface, CompletionItemKind::Module, + CompletionItemKind::Property, CompletionItemKind::Unit, CompletionItemKind::Value, + CompletionItemKind::Enum, CompletionItemKind::Keyword, CompletionItemKind::Snippet, + CompletionItemKind::Color, CompletionItemKind::File, CompletionItemKind::Reference, + CompletionItemKind::Folder, CompletionItemKind::EnumMember, CompletionItemKind::Constant, + CompletionItemKind::Struct, CompletionItemKind::Event, CompletionItemKind::Operator, + CompletionItemKind::TypeParameter}); +} + +std::optional<QList<CompletionItemKind::Kind>> +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities:: +valueSet() const +{ + if (std::optional<QList<int>> array = optionalArray<int>(valueSetKey)) { + return std::make_optional(Utils::transform(*array, [](int value) { + return static_cast<CompletionItemKind::Kind>(value); + })); + } + return std::nullopt; +} + +void +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities:: +setValueSet(const QList<CompletionItemKind::Kind> &valueSet) +{ + insert(valueSetKey, enumArrayToJsonArray<CompletionItemKind::Kind>(valueSet)); +} + +std::optional<QList<MarkupKind> > TextDocumentClientCapabilities::HoverCapabilities::contentFormat() const +{ + return optionalArray<MarkupKind>(contentFormatKey); +} + +void TextDocumentClientCapabilities::HoverCapabilities::setContentFormat(const QList<MarkupKind> &contentFormat) +{ + insertArray(contentFormatKey, contentFormat); +} + +std::optional<QList<MarkupKind>> +TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities:: +documentationFormat() const +{ + return optionalArray<MarkupKind>(documentationFormatKey); +} + +void +TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities:: +setDocumentationFormat(const QList<MarkupKind> &documentationFormat) +{ + insertArray(documentationFormatKey, documentationFormat); +} + +InitializeParams::InitializeParams() +{ + setProcessId(int(QCoreApplication::applicationPid())); + setRootUri(LanguageClientValue<DocumentUri>()); + setCapabilities(ClientCapabilities()); + setTrace(s_trace); +} + +std::optional<QJsonObject> InitializeParams::initializationOptions() const +{ + const QJsonValue &optionsValue = value(initializationOptionsKey); + if (optionsValue.isObject()) + return optionsValue.toObject(); + return std::nullopt; +} + +std::optional<Trace> InitializeParams::trace() const +{ + const QJsonValue &traceValue = value(traceKey); + if (traceValue.isUndefined()) + return std::nullopt; + return std::make_optional(Trace(traceValue.toString())); +} + +InitializeRequest::InitializeRequest(const InitializeParams ¶ms) + : Request(methodName, params) +{ } + +InitializeNotification::InitializeNotification(const InitializedParams ¶ms) + : Notification(methodName, params) +{ } + +} // namespace lsp diff --git a/src/shared/lsp/initializemessages.h b/src/shared/lsp/initializemessages.h new file mode 100644 index 000000000..159d62ded --- /dev/null +++ b/src/shared/lsp/initializemessages.h @@ -0,0 +1,187 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "clientcapabilities.h" +#include "jsonrpcmessages.h" +#include "lsptypes.h" +#include "servercapabilities.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT Trace +{ +public: + enum Values + { + off, + messages, + verbose + }; + + Trace() = default; + Trace(Values val) : m_value(val) {} + Trace(QString val) : Trace(fromString(val)) {} + + static Trace fromString(const QString& val); + QString toString() const; + +private: + Values m_value = off; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ClientInfo : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + std::optional<QString> version() const { return optionalValue<QString>(versionKey); } + void setVersion(const QString &version) { insert(versionKey, version); } + void clearVersion() { remove(versionKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeParams : public JsonObject +{ +public: + InitializeParams(); + using JsonObject::JsonObject; + + /* + * The process Id of the parent process that started + * the server. Is null if the process has not been started by another process. + * If the parent process is not alive then the server should exit (see exit notification) + * its process. + */ + LanguageClientValue<int> processId() const { return clientValue<int>(processIdKey); } + void setProcessId(const LanguageClientValue<int> &id) { insert(processIdKey, id); } + + /* + * The rootPath of the workspace. Is null + * if no folder is open. + * + * @deprecated in favor of rootUri. + */ + std::optional<LanguageClientValue<QString>> rootPath() const + { return optionalClientValue<QString>(rootPathKey); } + void setRootPath(const LanguageClientValue<QString> &path) + { insert(rootPathKey, path); } + void clearRootPath() { remove(rootPathKey); } + + /* + * The rootUri of the workspace. Is null if no + * folder is open. If both `rootPath` and `rootUri` are set + * `rootUri` wins. + */ + LanguageClientValue<DocumentUri> rootUri() const + { return clientValue<QString>(rootUriKey).transform<DocumentUri>(); } + void setRootUri(const LanguageClientValue<DocumentUri> &uri) + { insert(rootUriKey, uri); } + + // User provided initialization options. + std::optional<QJsonObject> initializationOptions() const; + void setInitializationOptions(const QJsonObject &options) + { insert(initializationOptionsKey, options); } + void clearInitializationOptions() { remove(initializationOptionsKey); } + + // The capabilities provided by the client (editor or tool) + ClientCapabilities capabilities() const { return typedValue<ClientCapabilities>(capabilitiesKey); } + void setCapabilities(const ClientCapabilities &capabilities) + { insert(capabilitiesKey, capabilities); } + + // The initial trace setting. If omitted trace is disabled ('off'). + std::optional<Trace> trace() const; + void setTrace(Trace trace) { insert(traceKey, trace.toString()); } + void clearTrace() { remove(traceKey); } + + /* + * The workspace folders configured in the client when the server starts. + * This property is only available if the client supports workspace folders. + * It can be `null` if the client supports workspace folders but none are + * configured. + * + * Since 3.6.0 + */ + std::optional<LanguageClientArray<WorkSpaceFolder>> workspaceFolders() const + { return optionalClientArray<WorkSpaceFolder>(workspaceFoldersKey); } + void setWorkSpaceFolders(const LanguageClientArray<WorkSpaceFolder> &folders) + { insert(workspaceFoldersKey, folders.toJson()); } + void clearWorkSpaceFolders() { remove(workspaceFoldersKey); } + + std::optional<ClientInfo> clientInfo() const { return optionalValue<ClientInfo>(clientInfoKey); } + void setClientInfo(const ClientInfo &clientInfo) { insert(clientInfoKey, clientInfo); } + void clearClientInfo() { remove(clientInfoKey); } + + bool isValid() const override + { return contains(processIdKey) && contains(rootUriKey) && contains(capabilitiesKey); } +}; + +using InitializedParams = JsonObject; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeNotification : public Notification<InitializedParams> +{ +public: + explicit InitializeNotification(const InitializedParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "initialized"; + + bool parametersAreValid(QString * /*errorMessage*/) const final { return true; } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ServerInfo : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + std::optional<QString> version() const { return optionalValue<QString>(versionKey); } + + bool isValid() const override { return contains(nameKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeResult : public JsonObject +{ +public: + using JsonObject::JsonObject; + + ServerCapabilities capabilities() const + { return typedValue<ServerCapabilities>(capabilitiesKey); } + void setCapabilities(const ServerCapabilities &capabilities) + { insert(capabilitiesKey, capabilities); } + + std::optional<ServerInfo> serverInfo() const + { return optionalValue<ServerInfo>(serverInfoKey); } + + bool isValid() const override { return contains(capabilitiesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeError : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /* + * Indicates whether the client execute the following retry logic: + * (1) show the message provided by the ResponseError to the user + * (2) user selects retry or cancel + * (3) if user selected retry the initialize method is sent again. + */ + bool retry() const { return typedValue<bool>(retryKey); } + void setRetry(const bool &retry) { insert(retryKey, retry); } + + bool isValid() const override { return contains(retryKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeRequest : public Request< + InitializeResult, InitializeError, InitializeParams> +{ +public: + explicit InitializeRequest(const InitializeParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "initialize"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/jsonkeys.h b/src/shared/lsp/jsonkeys.h new file mode 100644 index 000000000..5e5491caa --- /dev/null +++ b/src/shared/lsp/jsonkeys.h @@ -0,0 +1,231 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace lsp { + +constexpr char actionsKey[] = "actions"; +constexpr char activeParameterKey[] = "activeParameter"; +constexpr char activeParameterSupportKey[] = "activeParameterSupport"; +constexpr char activeSignatureKey[] = "activeSignature"; +constexpr char addedKey[] = "added"; +constexpr char additionalTextEditsKey[] = "additionalTextEdits"; +constexpr char alphaKey[] = "alpha"; +constexpr char appliedKey[] = "applied"; +constexpr char applyEditKey[] = "applyEdit"; +constexpr char argumentsKey[] = "arguments"; +constexpr char blueKey[] = "blue"; +constexpr char callHierarchyKey[] = "callHierarchy"; +constexpr char callHierarchyProviderKey[] = "callHierarchyProvider"; +constexpr char cancellableKey[] = "cancellable"; +constexpr char capabilitiesKey[] = "capabilities"; +constexpr char chKey[] = "ch"; +constexpr char changeKey[] = "change"; +constexpr char changeNotificationsKey[] = "changeNotifications"; +constexpr char changesKey[] = "changes"; +constexpr char characterKey[] = "character"; +constexpr char childrenKey[] = "children"; +constexpr char clientInfoKey[] = "clientInfo"; +constexpr char codeActionKey[] = "codeAction"; +constexpr char codeActionKindKey[] = "codeActionKind"; +constexpr char codeActionKindsKey[] = "codeActionKinds"; +constexpr char codeActionLiteralSupportKey[] = "codeActionLiteralSupport"; +constexpr char codeActionProviderKey[] = "codeActionProvider"; +constexpr char codeKey[] = "code"; +constexpr char codeLensKey[] = "codeLens"; +constexpr char codeLensProviderKey[] = "codeLensProvider"; +constexpr char colorInfoKey[] = "colorInfo"; +constexpr char colorKey[] = "color"; +constexpr char colorProviderKey[] = "colorProvider"; +constexpr char commandKey[] = "command"; +constexpr char commandsKey[] = "commands"; +constexpr char commitCharacterSupportKey[] = "commitCharacterSupport"; +constexpr char commitCharactersKey[] = "commitCharacters"; +constexpr char completionItemKey[] = "completionItem"; +constexpr char completionItemKindKey[] = "completionItemKind"; +constexpr char completionKey[] = "completion"; +constexpr char completionProviderKey[] = "completionProvider"; +constexpr char configurationKey[] = "configuration"; +constexpr char containerNameKey[] = "containerName"; +constexpr char contentChangesKey[] = "contentChanges"; +constexpr char contentFormatKey[] = "contentFormat"; +constexpr char contentKey[] = "value"; +constexpr char contentsKey[] = "contents"; +constexpr char contextKey[] = "context"; +constexpr char contextSupportKey[] = "contextSupport"; +constexpr char dataKey[] = "data"; +constexpr char definitionKey[] = "definition"; +constexpr char definitionProviderKey[] = "definitionProvider"; +constexpr char deleteCountKey[] = "deleteCount"; +constexpr char deltaKey[] = "delta"; +constexpr char deprecatedKey[] = "deprecated"; +constexpr char detailKey[] = "detail"; +constexpr char diagnosticsKey[] = "diagnostics"; +constexpr char didChangeConfigurationKey[] = "didChangeConfiguration"; +constexpr char didChangeWatchedFilesKey[] = "didChangeWatchedFiles"; +constexpr char didSaveKey[] = "didSave"; +constexpr char documentChangesKey[] = "documentChanges"; +constexpr char documentFormattingProviderKey[] = "documentFormattingProvider"; +constexpr char documentHighlightKey[] = "documentHighlight"; +constexpr char documentHighlightProviderKey[] = "documentHighlightProvider"; +constexpr char documentLinkKey[] = "documentLink"; +constexpr char documentLinkProviderKey[] = "documentLinkProvider"; +constexpr char documentRangeFormattingProviderKey[] = "documentRangeFormattingProvider"; +constexpr char documentSelectorKey[] = "documentSelector"; +constexpr char documentSymbolKey[] = "documentSymbol"; +constexpr char documentSymbolProviderKey[] = "documentSymbolProvider"; +constexpr char documentationFormatKey[] = "documentationFormat"; +constexpr char documentationKey[] = "documentation"; +constexpr char dynamicRegistrationKey[] = "dynamicRegistration"; +constexpr char editKey[] = "edit"; +constexpr char editsKey[] = "edits"; +constexpr char endKey[] = "end"; +constexpr char errorKey[] = "error"; +constexpr char eventKey[] = "event"; +constexpr char executeCommandKey[] = "executeCommand"; +constexpr char executeCommandProviderKey[] = "executeCommandProvider"; +constexpr char experimentalKey[] = "experimental"; +constexpr char filterTextKey[] = "filterText"; +constexpr char firstTriggerCharacterKey[] = "firstTriggerCharacter"; +constexpr char formatsKey[] = "formats"; +constexpr char formattingKey[] = "formatting"; +constexpr char fromKey[] = "from"; +constexpr char fromRangesKey[] = "fromRanges"; +constexpr char fullKey[] = "full"; +constexpr char greenKey[] = "green"; +constexpr char hierarchicalDocumentSymbolSupportKey[] = "hierarchicalDocumentSymbolSupport"; +constexpr char hoverKey[] = "hover"; +constexpr char hoverProviderKey[] = "hoverProvider"; +constexpr char idKey[] = "id"; +constexpr char ignoreIfExistsKey[] = "ignoreIfExists"; +constexpr char ignoreIfNotExistsKey[] = "ignoreIfNotExists"; +constexpr char implementationKey[] = "implementation"; +constexpr char implementationProviderKey[] = "implementationProvider"; +constexpr char includeDeclarationKey[] = "includeDeclaration"; +constexpr char includeTextKey[] = "includeText"; +constexpr char initializationOptionsKey[] = "initializationOptions"; +constexpr char insertFinalNewlineKey[] = "insertFinalNewline"; +constexpr char insertSpaceKey[] = "insertSpace"; +constexpr char insertTextFormatKey[] = "insertTextFormat"; +constexpr char insertTextKey[] = "insertText"; +constexpr char isIncompleteKey[] = "isIncomplete"; +constexpr char itemKey[] = "item"; +constexpr char itemsKey[] = "items"; +constexpr char jsonRpcVersionKey[] = "jsonrpc"; +constexpr char kindKey[] = "kind"; +constexpr char labelKey[] = "label"; +constexpr char languageIdKey[] = "languageId"; +constexpr char languageKey[] = "language"; +constexpr char legendKey[] = "legend"; +constexpr char limitKey[] = "limit"; +constexpr char lineKey[] = "line"; +constexpr char linesKey[] = "lines"; +constexpr char locationKey[] = "location"; +constexpr char messageKey[] = "message"; +constexpr char methodKey[] = "method"; +constexpr char moreTriggerCharacterKey[] = "moreTriggerCharacter"; +constexpr char multiLineTokenSupportKey[] = "multiLineTokenSupport"; +constexpr char nameKey[] = "name"; +constexpr char newNameKey[] = "newName"; +constexpr char newTextKey[] = "newText"; +constexpr char newUriKey[] = "newUri"; +constexpr char oldUriKey[] = "oldUri"; +constexpr char onTypeFormattingKey[] = "onTypeFormatting"; +constexpr char onlyKey[] = "only"; +constexpr char openCloseKey[] = "openClose"; +constexpr char optionsKey[] = "options"; +constexpr char overlappingTokenSupportKey[] = "overlappingTokenSupport"; +constexpr char overwriteKey[] = "overwrite"; +constexpr char parametersKey[] = "parameters"; +constexpr char paramsKey[] = "params"; +constexpr char patternKey[] = "pattern"; +constexpr char percentageKey[] = "percentage"; +constexpr char placeHolderKey[] = "placeHolder"; +constexpr char positionKey[] = "position"; +constexpr char prepareProviderKey[] = "prepareProvider"; +constexpr char prepareSupportKey[] = "prepareSupport"; +constexpr char previousResultIdKey[] = "previousResultId"; +constexpr char processIdKey[] = "processId"; +constexpr char queryKey[] = "query"; +constexpr char rangeFormattingKey[] = "rangeFormatting"; +constexpr char rangeKey[] = "range"; +constexpr char rangeLengthKey[] = "rangeLength"; +constexpr char reasonKey[] = "reason"; +constexpr char recursiveKey[] = "recursive"; +constexpr char redKey[] = "red"; +constexpr char referencesKey[] = "references"; +constexpr char referencesProviderKey[] = "referencesProvider"; +constexpr char refreshSupportKey[] = "refreshSupport"; +constexpr char registerOptionsKey[] = "registerOptions"; +constexpr char registrationsKey[] = "registrations"; +constexpr char removedKey[] = "removed"; +constexpr char renameKey[] = "rename"; +constexpr char renameProviderKey[] = "renameProvider"; +constexpr char requestsKey[] = "requests"; +constexpr char resolveProviderKey[] = "resolveProvider"; +constexpr char resourceOperationsKey[] = "resourceOperations"; +constexpr char resultIdKey[] = "resultId"; +constexpr char resultKey[] = "result"; +constexpr char retryKey[] = "retry"; +constexpr char rootPathKey[] = "rootPath"; +constexpr char rootUriKey[] = "rootUri"; +constexpr char saveKey[] = "save"; +constexpr char schemeKey[] = "scheme"; +constexpr char scopeUriKey[] = "scopeUri"; +constexpr char sectionKey[] = "section"; +constexpr char selectionRangeKey[] = "selectionRange"; +constexpr char semanticTokensKey[] = "semanticTokens"; +constexpr char semanticTokensProviderKey[] = "semanticTokensProvider"; +constexpr char serverInfoKey[] = "serverInfo"; +constexpr char settingsKey[] = "settings"; +constexpr char severityKey[] = "severity"; +constexpr char signatureHelpKey[] = "signatureHelp"; +constexpr char signatureHelpProviderKey[] = "signatureHelpProvider"; +constexpr char signatureInformationKey[] = "signatureInformation"; +constexpr char signaturesKey[] = "signatures"; +constexpr char snippetSupportKey[] = "snippetSupport"; +constexpr char sortTextKey[] = "sortText"; +constexpr char sourceKey[] = "source"; +constexpr char startKey[] = "start"; +constexpr char supportedKey[] = "supported"; +constexpr char symbolKey[] = "symbol"; +constexpr char symbolKindKey[] = "symbolKind"; +constexpr char syncKindKey[] = "syncKind"; +constexpr char synchronizationKey[] = "synchronization"; +constexpr char tabSizeKey[] = "tabSize"; +constexpr char tagsKey[] = "tags"; +constexpr char targetKey[] = "target"; +constexpr char textDocumentKey[] = "textDocument"; +constexpr char textDocumentSyncKey[] = "textDocumentSync"; +constexpr char textEditKey[] = "textEdit"; +constexpr char textKey[] = "text"; +constexpr char titleKey[] = "title"; +constexpr char toKey[] = "to"; +constexpr char tokenKey[] = "token"; +constexpr char tokenModifiersKey[] = "tokenModifiers"; +constexpr char tokenTypesKey[] = "tokenTypes"; +constexpr char traceKey[] = "trace"; +constexpr char triggerCharacterKey[] = "triggerCharacter"; +constexpr char triggerCharactersKey[] = "triggerCharacters"; +constexpr char triggerKindKey[] = "triggerKind"; +constexpr char trimFinalNewlinesKey[] = "trimFinalNewlines"; +constexpr char trimTrailingWhitespaceKey[] = "trimTrailingWhitespace"; +constexpr char typeDefinitionKey[] = "typeDefinition"; +constexpr char typeDefinitionProviderKey[] = "typeDefinitionProvider"; +constexpr char typeKey[] = "type"; +constexpr char unregistrationsKey[] = "unregistrations"; +constexpr char uriKey[] = "uri"; +constexpr char valueKey[] = "value"; +constexpr char valueSetKey[] = "valueSet"; +constexpr char versionKey[] = "version"; +constexpr char willSaveKey[] = "willSave"; +constexpr char willSaveWaitUntilKey[] = "willSaveWaitUntil"; +constexpr char windowKey[] = "window"; +constexpr char workDoneProgressKey[] = "workDoneProgress"; +constexpr char workspaceEditKey[] = "workspaceEdit"; +constexpr char workspaceFoldersKey[] = "workspaceFolders"; +constexpr char workspaceKey[] = "workspace"; +constexpr char workspaceSymbolProviderKey[] = "workspaceSymbolProvider"; + +} // namespace lsp diff --git a/src/shared/lsp/jsonobject.cpp b/src/shared/lsp/jsonobject.cpp new file mode 100644 index 000000000..887d79a5e --- /dev/null +++ b/src/shared/lsp/jsonobject.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "jsonobject.h" + +#include <QCoreApplication> + +namespace lsp { + +JsonObject &JsonObject::operator=(const JsonObject &other) = default; + +JsonObject &JsonObject::operator=(JsonObject &&other) +{ + m_jsonObject.swap(other.m_jsonObject); + return *this; +} + +QJsonObject::iterator JsonObject::insert(const std::string_view key, const JsonObject &object) +{ + return m_jsonObject.insert(QLatin1String(key.data()), object.m_jsonObject); +} + +QJsonObject::iterator JsonObject::insert(const std::string_view key, const QJsonValue &value) +{ + return m_jsonObject.insert(QLatin1String(key.data()), value); +} + +} // namespace lsp diff --git a/src/shared/lsp/jsonobject.h b/src/shared/lsp/jsonobject.h new file mode 100644 index 000000000..29811839f --- /dev/null +++ b/src/shared/lsp/jsonobject.h @@ -0,0 +1,181 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "algorithm.h" +#include "languageserverprotocol_global.h" +#include "lsputils.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QJsonObject> + +#include <optional> + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT JsonObject +{ +public: + using iterator = QJsonObject::iterator; + using const_iterator = QJsonObject::const_iterator; + + JsonObject() = default; + + explicit JsonObject(const QJsonObject &object) : m_jsonObject(object) { } + explicit JsonObject(QJsonObject &&object) : m_jsonObject(std::move(object)) { } + + JsonObject(const JsonObject &object) : m_jsonObject(object.m_jsonObject) { } + JsonObject(JsonObject &&object) : m_jsonObject(std::move(object.m_jsonObject)) { } + + explicit JsonObject(const QJsonValue &value) : m_jsonObject(value.toObject()) { } + + virtual ~JsonObject() = default; + + JsonObject &operator=(const JsonObject &other); + JsonObject &operator=(JsonObject &&other); + + bool operator==(const JsonObject &other) const { return m_jsonObject == other.m_jsonObject; } + + operator const QJsonObject&() const { return m_jsonObject; } + + virtual bool isValid() const { return true; } + + iterator end() { return m_jsonObject.end(); } + const_iterator end() const { return m_jsonObject.end(); } + + iterator insert(const std::string_view key, const JsonObject &value); + iterator insert(const std::string_view key, const QJsonValue &value); + +protected: + + template <typename T, typename V> + iterator insertVariant(const std::string_view key, const V &variant); + template <typename T1, typename T2, typename... Args, typename V> + iterator insertVariant(const std::string_view key, const V &variant); + + // QJSonObject redirections + QJsonValue value(const std::string_view key) const { return m_jsonObject.value(QLatin1String(key.data())); } + bool contains(const std::string_view key) const { return m_jsonObject.contains(QLatin1String(key.data())); } + iterator find(const std::string_view key) { return m_jsonObject.find(QLatin1String(key.data())); } + const_iterator find(const std::string_view key) const { return m_jsonObject.find(QLatin1String(key.data())); } + void remove(const std::string_view key) { m_jsonObject.remove(QLatin1String(key.data())); } + QStringList keys() const { return m_jsonObject.keys(); } + + // convenience value access + template<typename T> + T typedValue(const std::string_view key) const; + template<typename T> + std::optional<T> optionalValue(const std::string_view key) const; + template<typename T> + LanguageClientValue<T> clientValue(const std::string_view key) const; + template<typename T> + std::optional<LanguageClientValue<T>> optionalClientValue(const std::string_view key) const; + template<typename T> + QList<T> array(const std::string_view key) const; + template<typename T> + std::optional<QList<T>> optionalArray(const std::string_view key) const; + template<typename T> + LanguageClientArray<T> clientArray(const std::string_view key) const; + template<typename T> + std::optional<LanguageClientArray<T>> optionalClientArray(const std::string_view key) const; + template<typename T> + void insertArray(const std::string_view key, const QList<T> &array); + template<typename> + void insertArray(const std::string_view key, const QList<JsonObject> &array); + +private: + QJsonObject m_jsonObject; +}; + +template<typename T, typename V> +JsonObject::iterator JsonObject::insertVariant(const std::string_view key, const V &variant) +{ + return std::holds_alternative<T>(variant) ? insert(key, std::get<T>(variant)) : end(); +} + +template<typename T1, typename T2, typename... Args, typename V> +JsonObject::iterator JsonObject::insertVariant(const std::string_view key, const V &variant) +{ + auto result = insertVariant<T1>(key, variant); + return result != end() ? result : insertVariant<T2, Args...>(key, variant); +} + +template<typename T> +T JsonObject::typedValue(const std::string_view key) const +{ + return fromJsonValue<T>(value(key)); +} + +template<typename T> +std::optional<T> JsonObject::optionalValue(const std::string_view key) const +{ + const QJsonValue &val = value(key); + return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<T>(val)); +} + +template<typename T> +LanguageClientValue<T> JsonObject::clientValue(const std::string_view key) const +{ + return LanguageClientValue<T>(value(key)); +} + +template<typename T> +std::optional<LanguageClientValue<T>> JsonObject::optionalClientValue(const std::string_view key) const +{ + return contains(key) ? std::make_optional(clientValue<T>(key)) : std::nullopt; +} + +template<typename T> +QList<T> JsonObject::array(const std::string_view key) const +{ + if (const std::optional<QList<T>> &array = optionalArray<T>(key)) + return *array; + qCDebug(conversionLog) << QString("Expected array under %1 in:") + .arg(QLatin1String(key.data())) << *this; + return {}; +} + +template<typename T> +std::optional<QList<T>> JsonObject::optionalArray(const std::string_view key) const +{ + const QJsonValue &jsonValue = value(key); + if (jsonValue.isUndefined()) + return std::nullopt; + return Utils::transform<QList<T>>(jsonValue.toArray(), &fromJsonValue<T>); +} + +template<typename T> +LanguageClientArray<T> JsonObject::clientArray(const std::string_view key) const +{ + return LanguageClientArray<T>(value(key)); +} + +template<typename T> +std::optional<LanguageClientArray<T>> JsonObject::optionalClientArray(const std::string_view key) const +{ + const QJsonValue &val = value(key); + return !val.isUndefined() ? std::make_optional(LanguageClientArray<T>(value(key))) + : std::nullopt; +} + +template<typename T> +void JsonObject::insertArray(const std::string_view key, const QList<T> &array) +{ + QJsonArray jsonArray; + for (const T &item : array) + jsonArray.append(QJsonValue(item)); + insert(key, jsonArray); +} + +template<typename > +void JsonObject::insertArray(const std::string_view key, const QList<JsonObject> &array) +{ + QJsonArray jsonArray; + for (const JsonObject &item : array) + jsonArray.append(item.m_jsonObject); + insert(key, jsonArray); +} + +} // namespace lsp diff --git a/src/shared/lsp/jsonrpcmessages.cpp b/src/shared/lsp/jsonrpcmessages.cpp new file mode 100644 index 000000000..dbe354bc4 --- /dev/null +++ b/src/shared/lsp/jsonrpcmessages.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "jsonrpcmessages.h" + +#include "initializemessages.h" +#include "languageserverprotocoltr.h" +#include "lsputils.h" + +#include <QCoreApplication> +#include <QObject> +#include <QJsonDocument> +#include <QTextCodec> + +namespace lsp { +Q_LOGGING_CATEGORY(timingLog, "qtc.languageserverprotocol.timing", QtWarningMsg) + +constexpr const char CancelRequest::methodName[]; + +QByteArray JsonRpcMessage::toRawData() const +{ + return QJsonDocument(m_jsonObject).toJson(QJsonDocument::Compact); +} + +bool JsonRpcMessage::isValid(QString *errorMessage) const +{ + if (!m_parseError.isEmpty()) { + if (errorMessage) + *errorMessage = m_parseError; + return false; + } + return m_jsonObject[jsonRpcVersionKey] == "2.0"; +} + +const QJsonObject &JsonRpcMessage::toJsonObject() const +{ + return m_jsonObject; +} + +JsonRpcMessage::JsonRpcMessage() +{ + // The language server protocol always uses “2.0” as the jsonrpc version + m_jsonObject[jsonRpcVersionKey] = "2.0"; +} + +constexpr int utf8mib = 106; + +static QString docType(const QJsonDocument &doc) +{ + if (doc.isArray()) + return QString("array"); + if (doc.isEmpty()) + return QString("empty"); + if (doc.isNull()) + return QString("null"); + if (doc.isObject()) + return QString("object"); + return {}; +} + +JsonRpcMessage::JsonRpcMessage(const BaseMessage &message) +{ + if (message.content.isEmpty()) + return; + QByteArray content; + if (message.codec && message.codec->mibEnum() != utf8mib) { + QTextCodec *utf8 = QTextCodec::codecForMib(utf8mib); + if (utf8) + content = utf8->fromUnicode(message.codec->toUnicode(message.content)); + } + if (content.isEmpty()) + content = message.content; + QJsonParseError error = {0, QJsonParseError::NoError}; + const QJsonDocument doc = QJsonDocument::fromJson(content, &error); + if (doc.isObject()) + m_jsonObject = doc.object(); + else if (doc.isNull()) + m_parseError = Tr::tr("Could not parse JSON message: \"%1\".").arg(error.errorString()); + else + m_parseError = + Tr::tr("Expected a JSON object, but got a JSON \"%1\" value.").arg(docType(doc)); +} + +JsonRpcMessage::JsonRpcMessage(const QJsonObject &jsonObject) + : m_jsonObject(jsonObject) +{ } + +JsonRpcMessage::JsonRpcMessage(QJsonObject &&jsonObject) + : m_jsonObject(std::move(jsonObject)) +{ } + +QByteArray JsonRpcMessage::jsonRpcMimeType() +{ + return "application/vscode-jsonrpc"; +} + +CancelRequest::CancelRequest(const CancelParameter ¶ms) + : Notification(methodName, params) +{ } + +void logElapsedTime(const QString &method, const QElapsedTimer &t) +{ + qCDebug(timingLog) << "received server reply to" << method + << "after" << t.elapsed() << "ms"; +} + +} // namespace lsp diff --git a/src/shared/lsp/jsonrpcmessages.h b/src/shared/lsp/jsonrpcmessages.h new file mode 100644 index 000000000..a523dd5e1 --- /dev/null +++ b/src/shared/lsp/jsonrpcmessages.h @@ -0,0 +1,415 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "basemessage.h" +#include "jsonkeys.h" +#include "lsptypes.h" + +#include <QDebug> +#include <QElapsedTimer> +#include <QHash> +#include <QJsonObject> +#include <QJsonValue> +#include <QCoreApplication> +#include <QUuid> + +#include <optional> +#include <variant> + +namespace lsp { + +class JsonRpcMessage; + +class LANGUAGESERVERPROTOCOL_EXPORT MessageId : public std::variant<int, QString> +{ +public: + MessageId() : variant(QString()) {} + explicit MessageId(int id) : variant(id) {} + explicit MessageId(const QString &id) : variant(id) {} + explicit MessageId(const QJsonValue &value) + { + if (value.isDouble()) + emplace<int>(value.toInt()); + else + emplace<QString>(value.toString()); + } + + operator QJsonValue() const + { + if (auto id = std::get_if<int>(this)) + return *id; + if (auto id = std::get_if<QString>(this)) + return *id; + return QJsonValue(); + } + + bool isValid() const + { + if (std::holds_alternative<int>(*this)) + return true; + const QString *id = std::get_if<QString>(this); + QBS_ASSERT(id, return false); + return !id->isEmpty(); + } + + QString toString() const + { + if (auto id = std::get_if<QString>(this)) + return *id; + if (auto id = std::get_if<int>(this)) + return QString::number(*id); + return {}; + } + +private: + friend size_t qHash(const MessageId &id) + { + if (std::holds_alternative<int>(id)) + return QT_PREPEND_NAMESPACE(qHash(std::get<int>(id))); + if (std::holds_alternative<QString>(id)) + return QT_PREPEND_NAMESPACE(qHash(std::get<QString>(id))); + return QT_PREPEND_NAMESPACE(qHash(0)); + } + + friend QDebug operator<<(QDebug stream, const MessageId &id) + { + if (std::holds_alternative<int>(id)) + stream << std::get<int>(id); + else + stream << std::get<QString>(id); + return stream; + } +}; + +struct ResponseHandler +{ + MessageId id; + using Callback = std::function<void(const JsonRpcMessage &)>; + Callback callback; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessage +{ +public: + JsonRpcMessage(); + explicit JsonRpcMessage(const BaseMessage &message); + explicit JsonRpcMessage(const QJsonObject &jsonObject); + explicit JsonRpcMessage(QJsonObject &&jsonObject); + + virtual ~JsonRpcMessage() = default; + + static QByteArray jsonRpcMimeType(); + + QByteArray toRawData() const; + virtual bool isValid(QString *errorMessage) const; + + const QJsonObject &toJsonObject() const; + + const QString parseError() { return m_parseError; } + + virtual std::optional<ResponseHandler> responseHandler() const { return std::nullopt; } + + BaseMessage toBaseMessage() const + { return BaseMessage(jsonRpcMimeType(), toRawData()); } + +protected: + QJsonObject m_jsonObject; + +private: + QString m_parseError; +}; + +template <typename Params> +class Notification : public JsonRpcMessage +{ +public: + Notification(const QString &methodName, const Params ¶ms) + { + setMethod(methodName); + setParams(params); + } + + explicit Notification(const QJsonObject &jsonObject) : JsonRpcMessage(jsonObject) {} + explicit Notification(QJsonObject &&jsonObject) : JsonRpcMessage(std::move(jsonObject)) {} + + QString method() const + { return fromJsonValue<QString>(m_jsonObject.value(methodKey)); } + void setMethod(const QString &method) + { m_jsonObject.insert(methodKey, method); } + + using Parameters = Params; + std::optional<Params> params() const + { + const QJsonValue ¶ms = m_jsonObject.value(paramsKey); + return params.isUndefined() ? std::nullopt : std::make_optional(Params(params)); + } + void setParams(const Params ¶ms) + { m_jsonObject.insert(paramsKey, QJsonValue(params)); } + void clearParams() { m_jsonObject.remove(paramsKey); } + + bool isValid(QString *errorMessage) const override + { + return JsonRpcMessage::isValid(errorMessage) + && m_jsonObject.value(methodKey).isString() + && parametersAreValid(errorMessage); + } + + virtual bool parametersAreValid(QString *errorMessage) const + { + if (auto parameter = params()) + return parameter->isValid(); + if (errorMessage) + *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol", + "No parameters in \"%1\".").arg(method()); + return false; + } +}; + +template <> +class Notification<std::nullptr_t> : public JsonRpcMessage +{ +public: + Notification() : Notification(QString()) {} + explicit Notification(const QString &methodName, const std::nullptr_t &/*params*/ = nullptr) + { + setMethod(methodName); + setParams(nullptr); + } + using JsonRpcMessage::JsonRpcMessage; + + QString method() const + { return fromJsonValue<QString>(m_jsonObject.value(methodKey)); } + void setMethod(const QString &method) + { m_jsonObject.insert(methodKey, method); } + + std::optional<std::nullptr_t> params() const + { return nullptr; } + void setParams(const std::nullptr_t &/*params*/) + { m_jsonObject.insert(paramsKey, QJsonValue::Null); } + void clearParams() { m_jsonObject.remove(paramsKey); } + + bool isValid(QString *errorMessage) const override + { + return JsonRpcMessage::isValid(errorMessage) + && m_jsonObject.value(methodKey).isString() + && parametersAreValid(errorMessage); + } + + virtual bool parametersAreValid(QString * /*errorMessage*/) const + { return true; } +}; + +template <typename Error> +class ResponseError : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int code() const { return typedValue<int>(codeKey); } + void setCode(int code) { insert(codeKey, code); } + + QString message() const { return typedValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + + std::optional<Error> data() const { return optionalValue<Error>(dataKey); } + void setData(const Error &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid() const override { return contains(codeKey) && contains(messageKey); } + + QString toString() const { return errorCodesToString(code()) + ": " + message(); } + + // predefined error codes + enum ErrorCodes { + // Defined by JSON RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + serverErrorStart = -32099, + serverErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + // Defined by the protocol. + RequestCancelled = -32800 + }; + +#define CASE_ERRORCODES(x) case ErrorCodes::x: return QLatin1String(#x) + static QString errorCodesToString(int code) + { + switch (code) { + CASE_ERRORCODES(ParseError); + CASE_ERRORCODES(InvalidRequest); + CASE_ERRORCODES(MethodNotFound); + CASE_ERRORCODES(InvalidParams); + CASE_ERRORCODES(InternalError); + CASE_ERRORCODES(serverErrorStart); + CASE_ERRORCODES(serverErrorEnd); + CASE_ERRORCODES(ServerNotInitialized); + CASE_ERRORCODES(RequestCancelled); + default: + return QCoreApplication::translate("QtC::LanguageClient", "Error %1").arg(code); + } + } +#undef CASE_ERRORCODES +}; + +template <typename Result, typename ErrorDataType> +class Response : public JsonRpcMessage +{ +public: + explicit Response(const MessageId &id) { setId(id); } + using JsonRpcMessage::JsonRpcMessage; + + MessageId id() const + { return MessageId(m_jsonObject.value(idKey)); } + void setId(MessageId id) + { this->m_jsonObject.insert(idKey, id); } + + std::optional<Result> result() const + { + const QJsonValue &result = m_jsonObject.value(resultKey); + if (result.isUndefined()) + return std::nullopt; + return std::make_optional(Result(result)); + } + void setResult(const Result &result) { m_jsonObject.insert(resultKey, QJsonValue(result)); } + void clearResult() { m_jsonObject.remove(resultKey); } + + using Error = ResponseError<ErrorDataType>; + std::optional<Error> error() const + { + const QJsonValue &val = m_jsonObject.value(errorKey); + return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<Error>(val)); + } + void setError(const Error &error) + { + m_jsonObject.insert(errorKey, QJsonValue(error)); + } + void clearError() { m_jsonObject.remove(errorKey); } + + bool isValid(QString *errorMessage) const override + { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); } +}; + +template<typename ErrorDataType> +class Response<std::nullptr_t, ErrorDataType> : public JsonRpcMessage +{ +public: + explicit Response(const MessageId &id) { setId(id); } + using JsonRpcMessage::JsonRpcMessage; + + MessageId id() const + { return MessageId(m_jsonObject.value(idKey)); } + void setId(MessageId id) + { this->m_jsonObject.insert(idKey, id); } + + std::optional<std::nullptr_t> result() const + { + return m_jsonObject.value(resultKey).isNull() ? std::make_optional(nullptr) : std::nullopt; + } + void setResult(const std::nullptr_t &) { m_jsonObject.insert(resultKey, QJsonValue::Null); } + void clearResult() { m_jsonObject.remove(resultKey); } + + using Error = ResponseError<ErrorDataType>; + std::optional<Error> error() const + { + const QJsonValue &val = m_jsonObject.value(errorKey); + return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<Error>(val)); + } + void setError(const Error &error) + { m_jsonObject.insert(errorKey, QJsonValue(error)); } + void clearError() { m_jsonObject.remove(errorKey); } + + bool isValid(QString *errorMessage) const override + { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); } +}; + +void LANGUAGESERVERPROTOCOL_EXPORT logElapsedTime(const QString &method, const QElapsedTimer &t); + +template <typename Result, typename ErrorDataType, typename Params> +class Request : public Notification<Params> +{ +public: + Request(const QString &methodName, const Params ¶ms) + : Notification<Params>(methodName, params) + { setId(MessageId(QUuid::createUuid().toString())); } + explicit Request(const QJsonObject &jsonObject) : Notification<Params>(jsonObject) { } + explicit Request(QJsonObject &&jsonObject) : Notification<Params>(std::move(jsonObject)) { } + + MessageId id() const + { return MessageId(JsonRpcMessage::m_jsonObject.value(idKey)); } + void setId(const MessageId &id) + { JsonRpcMessage::m_jsonObject.insert(idKey, id); } + + using Response = lsp::Response<Result, ErrorDataType>; + using ResponseCallback = std::function<void(Response)>; + void setResponseCallback(const ResponseCallback &callback) + { m_callBack = callback; } + + std::optional<ResponseHandler> responseHandler() const final + { + QElapsedTimer timer; + timer.start(); + auto callback = [callback = m_callBack, method = this->method(), t = std::move(timer)] + (const JsonRpcMessage &message) { + if (!callback) + return; + logElapsedTime(method, t); + + callback(Response(message.toJsonObject())); + }; + return std::make_optional(ResponseHandler{id(), callback}); + } + + bool isValid(QString *errorMessage) const override + { + if (!Notification<Params>::isValid(errorMessage)) + return false; + if (id().isValid()) + return true; + if (errorMessage) + *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol", + "No ID set in \"%1\".").arg(this->method()); + return false; + } + +private: + ResponseCallback m_callBack; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CancelParameter : public JsonObject +{ +public: + explicit CancelParameter(const MessageId &id) { setId(id); } + CancelParameter() = default; + using JsonObject::JsonObject; + + MessageId id() const { return typedValue<MessageId>(idKey); } + void setId(const MessageId &id) { insert(idKey, id); } + + bool isValid() const override { return contains(idKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CancelRequest : public Notification<CancelParameter> +{ +public: + explicit CancelRequest(const CancelParameter ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "$/cancelRequest"; +}; + +} // namespace lsp + +template <typename Error> +inline QDebug operator<<(QDebug stream, const lsp::ResponseError<Error> &error) +{ + stream.nospace() << error.toString(); + return stream; +} + +Q_DECLARE_METATYPE(lsp::JsonRpcMessage) diff --git a/src/shared/lsp/languagefeatures.cpp b/src/shared/lsp/languagefeatures.cpp new file mode 100644 index 000000000..8ca771f68 --- /dev/null +++ b/src/shared/lsp/languagefeatures.cpp @@ -0,0 +1,399 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "languagefeatures.h" + +#include <cstddef> + +namespace lsp { + +constexpr const char HoverRequest::methodName[]; +constexpr const char GotoDefinitionRequest::methodName[]; +constexpr const char GotoTypeDefinitionRequest::methodName[]; +constexpr const char GotoImplementationRequest::methodName[]; +constexpr const char FindReferencesRequest::methodName[]; +constexpr const char DocumentHighlightsRequest::methodName[]; +constexpr const char DocumentSymbolsRequest::methodName[]; +constexpr const char CodeActionRequest::methodName[]; +constexpr const char CodeLensRequest::methodName[]; +constexpr const char CodeLensResolveRequest::methodName[]; +constexpr const char DocumentLinkRequest::methodName[]; +constexpr const char DocumentLinkResolveRequest::methodName[]; +constexpr const char DocumentColorRequest::methodName[]; +constexpr const char ColorPresentationRequest::methodName[]; +constexpr const char DocumentFormattingRequest::methodName[]; +constexpr const char DocumentRangeFormattingRequest::methodName[]; +constexpr const char DocumentOnTypeFormattingRequest::methodName[]; +constexpr const char RenameRequest::methodName[]; +constexpr const char SignatureHelpRequest::methodName[]; +constexpr const char PrepareRenameRequest::methodName[]; + +HoverContent Hover::content() const +{ + return HoverContent(value(contentsKey)); +} + +void Hover::setContent(const HoverContent &content) +{ + if (auto val = std::get_if<MarkedString>(&content)) + insert(contentsKey, *val); + else if (auto val = std::get_if<MarkupContent>(&content)) + insert(contentsKey, *val); + else if (auto val = std::get_if<QList<MarkedString>>(&content)) + insert(contentsKey, LanguageClientArray<MarkedString>(*val).toJson()); + else + QBS_ASSERT("LanguageClient Using unknown type Hover::setContent",;); +} + +HoverRequest::HoverRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +std::optional<MarkupOrString> ParameterInformation::documentation() const +{ + QJsonValue documentation = value(documentationKey); + if (documentation.isUndefined()) + return std::nullopt; + return MarkupOrString(documentation); +} + +GotoDefinitionRequest::GotoDefinitionRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +GotoTypeDefinitionRequest::GotoTypeDefinitionRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +GotoImplementationRequest::GotoImplementationRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +FindReferencesRequest::FindReferencesRequest(const ReferenceParams ¶ms) + : Request(methodName, params) +{ } + +DocumentHighlightsRequest::DocumentHighlightsRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +DocumentSymbolsRequest::DocumentSymbolsRequest(const DocumentSymbolParams ¶ms) + : Request(methodName, params) +{ } + +std::optional<QList<CodeActionKind> > CodeActionParams::CodeActionContext::only() const +{ + return optionalArray<CodeActionKind>(onlyKey); +} + +void CodeActionParams::CodeActionContext::setOnly(const QList<CodeActionKind> &only) +{ + insertArray(onlyKey, only); +} + +CodeActionRequest::CodeActionRequest(const CodeActionParams ¶ms) + : Request(methodName, params) +{ } + +CodeLensRequest::CodeLensRequest(const CodeLensParams ¶ms) + : Request(methodName, params) +{ } + +CodeLensResolveRequest::CodeLensResolveRequest(const CodeLens ¶ms) + : Request(methodName, params) +{ } + +DocumentLinkRequest::DocumentLinkRequest(const DocumentLinkParams ¶ms) + : Request(methodName, params) +{ } + +DocumentLinkResolveRequest::DocumentLinkResolveRequest(const DocumentLink ¶ms) + : Request(methodName, params) +{ } + +DocumentColorRequest::DocumentColorRequest(const DocumentColorParams ¶ms) + : Request(methodName, params) +{ } + +ColorPresentationRequest::ColorPresentationRequest(const ColorPresentationParams ¶ms) + : Request(methodName, params) +{ } + +QHash<QString, DocumentFormattingProperty> FormattingOptions::properties() const +{ + QHash<QString, DocumentFormattingProperty> ret; + for (const QString &key : keys()) { + if (key == tabSizeKey || key == insertSpaceKey) + continue; + QJsonValue property = value(key.toStdString()); + if (property.isBool()) + ret[key] = property.toBool(); + if (property.isDouble()) + ret[key] = property.toDouble(); + if (property.isString()) + ret[key] = property.toString(); + } + return ret; +} + +void FormattingOptions::setProperty(const std::string_view key, const DocumentFormattingProperty &property) +{ + using namespace std; + if (auto val = get_if<double>(&property)) + insert(key, *val); + else if (auto val = get_if<QString>(&property)) + insert(key, *val); + else if (auto val = get_if<bool>(&property)) + insert(key, *val); +} + +DocumentFormattingRequest::DocumentFormattingRequest(const DocumentFormattingParams ¶ms) + : Request(methodName, params) +{ } + +DocumentRangeFormattingRequest::DocumentRangeFormattingRequest( + const DocumentRangeFormattingParams ¶ms) + : Request(methodName, params) +{ } + +bool DocumentOnTypeFormattingParams::isValid() const +{ + return contains(textDocumentKey) && contains(positionKey) && contains(chKey) + && contains(optionsKey); +} + +DocumentOnTypeFormattingRequest::DocumentOnTypeFormattingRequest( + const DocumentOnTypeFormattingParams ¶ms) + : Request(methodName, params) +{ } + +PrepareRenameRequest::PrepareRenameRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +bool RenameParams::isValid() const +{ + return contains(textDocumentKey) && contains(positionKey) && contains(newNameKey); +} + +RenameRequest::RenameRequest(const RenameParams ¶ms) + : Request(methodName, params) +{ } + +std::optional<DocumentUri> DocumentLink::target() const +{ + if (std::optional<QString> optionalTarget = optionalValue<QString>(targetKey)) + return std::make_optional(DocumentUri::fromProtocol(*optionalTarget)); + return std::nullopt; +} + +std::optional<QJsonValue> DocumentLink::data() const +{ + return contains(dataKey) ? std::make_optional(value(dataKey)) : std::nullopt; +} + +TextDocumentParams::TextDocumentParams() + : TextDocumentParams(TextDocumentIdentifier()) +{ } + +TextDocumentParams::TextDocumentParams(const TextDocumentIdentifier &identifier) + : JsonObject() +{ + setTextDocument(identifier); +} + +GotoResult::GotoResult(const QJsonValue &value) +{ + if (value.isArray()) { + QList<Location> locations; + for (auto arrayValue : value.toArray()) { + if (arrayValue.isObject()) + locations.append(Location(arrayValue.toObject())); + } + emplace<QList<Location>>(locations); + } else if (value.isObject()) { + emplace<Location>(value.toObject()); + } else { + emplace<std::nullptr_t>(nullptr); + } +} + +template<typename Symbol> +QList<Symbol> documentSymbolsResultArray(const QJsonArray &array) +{ + QList<Symbol> ret; + for (const auto &arrayValue : array) { + if (arrayValue.isObject()) + ret << Symbol(arrayValue.toObject()); + } + return ret; +} + +DocumentSymbolsResult::DocumentSymbolsResult(const QJsonValue &value) +{ + if (value.isArray()) { + QJsonArray array = value.toArray(); + if (array.isEmpty()) { + *this = QList<SymbolInformation>(); + } else { + QJsonObject arrayObject = array.first().toObject(); + if (arrayObject.contains(rangeKey)) + *this = documentSymbolsResultArray<DocumentSymbol>(array); + else + *this = documentSymbolsResultArray<SymbolInformation>(array); + } + } else { + *this = nullptr; + } +} + +DocumentHighlightsResult::DocumentHighlightsResult(const QJsonValue &value) +{ + if (value.isArray()) { + QList<DocumentHighlight> highlights; + for (auto arrayValue : value.toArray()) { + if (arrayValue.isObject()) + highlights.append(DocumentHighlight(arrayValue.toObject())); + } + *this = highlights; + } else { + *this = nullptr; + } +} + +MarkedString::MarkedString(const QJsonValue &value) +{ + if (value.isObject()) + emplace<MarkedLanguageString>(MarkedLanguageString(value.toObject())); + else + emplace<QString>(value.toString()); +} + +bool MarkedString::isValid() const +{ + if (auto markedLanguageString = std::get_if<MarkedLanguageString>(this)) + return markedLanguageString->isValid(); + return true; +} + +MarkedString::operator QJsonValue() const +{ + if (auto val = std::get_if<QString>(this)) + return *val; + if (auto val = std::get_if<MarkedLanguageString>(this)) + return QJsonValue(*val); + return {}; +} + +HoverContent::HoverContent(const QJsonValue &value) +{ + if (value.isArray()) { + emplace<QList<MarkedString>>(LanguageClientArray<MarkedString>(value).toList()); + } else if (value.isObject()) { + const QJsonObject &object = value.toObject(); + MarkedLanguageString markedLanguageString(object); + if (markedLanguageString.isValid()) + emplace<MarkedString>(markedLanguageString); + else + emplace<MarkupContent>(MarkupContent(object)); + } else if (value.isString()) { + emplace<MarkedString>(MarkedString(value.toString())); + } +} + +bool HoverContent::isValid() const +{ + if (std::holds_alternative<MarkedString>(*this)) + return std::get<MarkedString>(*this).isValid(); + return true; +} + +DocumentFormattingProperty::DocumentFormattingProperty(const QJsonValue &value) +{ + if (value.isBool()) + *this = value.toBool(); + if (value.isDouble()) + *this = value.toDouble(); + if (value.isString()) + *this = value.toString(); +} + +SignatureHelpRequest::SignatureHelpRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +CodeActionResult::CodeActionResult(const QJsonValue &val) +{ + using ResultArray = QList<std::variant<Command, CodeAction>>; + if (val.isArray()) { + const QJsonArray array = val.toArray(); + ResultArray result; + for (const QJsonValue &val : array) { + if (val.toObject().value(commandKey).isString()) { + const Command command(val); + if (command.isValid()) + result << command; + } else { + const CodeAction action(val); + if (action.isValid()) + result << action; + } + } + emplace<ResultArray>(result); + return; + } + emplace<std::nullptr_t>(nullptr); +} + +PrepareRenameResult::PrepareRenameResult() + : std::variant<PlaceHolderResult, Range, std::nullptr_t>(nullptr) +{} + +PrepareRenameResult::PrepareRenameResult( + const std::variant<PlaceHolderResult, Range, std::nullptr_t> &val) + : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val) +{} + +PrepareRenameResult::PrepareRenameResult(const PlaceHolderResult &val) + : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val) + +{} + +PrepareRenameResult::PrepareRenameResult(const Range &val) + : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val) +{} + +PrepareRenameResult::PrepareRenameResult(const QJsonValue &val) +{ + if (val.isNull()) { + emplace<std::nullptr_t>(nullptr); + } else if (val.isObject()) { + const QJsonObject object = val.toObject(); + if (object.keys().contains(rangeKey)) + emplace<PlaceHolderResult>(PlaceHolderResult(object)); + else + emplace<Range>(Range(object)); + } +} + +std::optional<QJsonValue> CodeLens::data() const +{ + return contains(dataKey) ? std::make_optional(value(dataKey)) : std::nullopt; +} + +HoverResult::HoverResult(const QJsonValue &value) +{ + if (value.isObject()) + emplace<Hover>(Hover(value.toObject())); + else + emplace<std::nullptr_t>(nullptr); +} + +bool HoverResult::isValid() const +{ + if (auto hover = std::get_if<Hover>(this)) + return hover->isValid(); + return true; +} + +} // namespace lsp diff --git a/src/shared/lsp/languagefeatures.h b/src/shared/lsp/languagefeatures.h new file mode 100644 index 000000000..13e3e9857 --- /dev/null +++ b/src/shared/lsp/languagefeatures.h @@ -0,0 +1,836 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +/** + * MarkedString can be used to render human readable text. It is either a markdown string + * or a code-block that provides a language and a code snippet. The language identifier + * is semantically equal to the optional language identifier in fenced code blocks in GitHub + * issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * The pair of a language and a value is an equivalent to markdown: + * ```${language} + * ${value} + * ``` + * + * Note that markdown strings will be sanitized - that means html will be escaped. +* @deprecated use MarkupContent instead. +*/ +class LANGUAGESERVERPROTOCOL_EXPORT MarkedLanguageString : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString language() const { return typedValue<QString>(languageKey); } + void setLanguage(const QString &language) { insert(languageKey, language); } + + QString value() const { return typedValue<QString>(valueKey); } + void setValue(const QString &value) { insert(valueKey, value); } + + bool isValid() const override { return contains(languageKey) && contains(valueKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkedString + : public std::variant<QString, MarkedLanguageString> +{ +public: + MarkedString() = default; + explicit MarkedString(const MarkedLanguageString &other) + : variant(other) + {} + explicit MarkedString(const QString &other) + : variant(other) + {} + explicit MarkedString(const QJsonValue &value); + + bool isValid() const; + operator QJsonValue() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT HoverContent + : public std::variant<MarkedString, QList<MarkedString>, MarkupContent> +{ +public: + HoverContent() = default; + explicit HoverContent(const MarkedString &other) : variant(other) {} + explicit HoverContent(const QList<MarkedString> &other) : variant(other) {} + explicit HoverContent(const MarkupContent &other) : variant(other) {} + explicit HoverContent(const QJsonValue &value); + bool isValid() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Hover : public JsonObject +{ +public: + using JsonObject::JsonObject; + + HoverContent content() const; + void setContent(const HoverContent &content); + + std::optional<Range> range() const { return optionalValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + void clearRange() { remove(rangeKey); } + + bool isValid() const override { return contains(contentsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT HoverResult : public std::variant<Hover, std::nullptr_t> +{ +public: + HoverResult() : variant(nullptr) {} + explicit HoverResult(const Hover &hover) : variant(hover) {} + explicit HoverResult(const QJsonValue &value); + bool isValid() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT HoverRequest + : public Request<HoverResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit HoverRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/hover"; +}; + +/** + * Represents a parameter of a callable-signature. A parameter can + * have a label and a doc-comment. + */ +class LANGUAGESERVERPROTOCOL_EXPORT ParameterInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + std::optional<MarkupOrString> documentation() const; + void setDocumentation(const MarkupOrString &documentation) + { insert(documentationKey, documentation.toJson()); } + void clearDocumentation() { remove(documentationKey); } + + bool isValid() const override { return contains(labelKey); } +}; + +/** + * Represents the signature of something callable. A signature + * can have a label, like a function-name, a doc-comment, and + * a set of parameters. + */ +class LANGUAGESERVERPROTOCOL_EXPORT SignatureInformation : public ParameterInformation +{ +public: + using ParameterInformation::ParameterInformation; + + std::optional<QList<ParameterInformation>> parameters() const + { return optionalArray<ParameterInformation>(parametersKey); } + void setParameters(const QList<ParameterInformation> ¶meters) + { insertArray(parametersKey, parameters); } + void clearParameters() { remove(parametersKey); } + + std::optional<int> activeParameter() const { return optionalValue<int>(activeParameterKey); } + void setActiveParameter(int activeParameter) { insert(activeParameterKey, activeParameter); } + void clearActiveParameter() { remove(activeParameterKey); } +}; + +/** + * Signature help represents the signature of something + * callable. There can be multiple signature but only one + * active and only one active parameter. + */ +class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelp : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /// One or more signatures. + QList<SignatureInformation> signatures() const + { return array<SignatureInformation>(signaturesKey); } + void setSignatures(const QList<SignatureInformation> &signatures) + { insertArray(signaturesKey, signatures); } + + /** + * The active signature. If omitted or the value lies outside the + * range of `signatures` the value defaults to zero or is ignored if + * `signatures.length === 0`. Whenever possible implementors should + * make an active decision about the active signature and shouldn't + * rely on a default value. + * In future version of the protocol this property might become + * mandatory to better express this. + */ + std::optional<int> activeSignature() const { return optionalValue<int>(activeSignatureKey); } + void setActiveSignature(int activeSignature) { insert(activeSignatureKey, activeSignature); } + void clearActiveSignature() { remove(activeSignatureKey); } + + /** + * The active parameter of the active signature. If omitted or the value + * lies outside the range of `signatures[activeSignature].parameters` + * defaults to 0 if the active signature has parameters. If + * the active signature has no parameters it is ignored. + * In future version of the protocol this property might become + * mandatory to better express the active parameter if the + * active signature does have any. + */ + std::optional<int> activeParameter() const { return optionalValue<int>(activeParameterKey); } + void setActiveParameter(int activeParameter) { insert(activeParameterKey, activeParameter); } + void clearActiveParameter() { remove(activeParameterKey); } + + bool isValid() const override { return contains(signaturesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpRequest + : public Request<LanguageClientValue<SignatureHelp>, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit SignatureHelpRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/signatureHelp"; +}; + +/// The result of a goto request can either be a location, a list of locations or null +class LANGUAGESERVERPROTOCOL_EXPORT GotoResult + : public std::variant<Location, QList<Location>, std::nullptr_t> +{ +public: + explicit GotoResult(const QJsonValue &value); + using variant::variant; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoDefinitionRequest + : public Request<GotoResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit GotoDefinitionRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/definition"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoTypeDefinitionRequest : public Request< + GotoResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit GotoTypeDefinitionRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/typeDefinition"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoImplementationRequest : public Request< + GotoResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit GotoImplementationRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/implementation"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ReferenceParams : public TextDocumentPositionParams +{ +public: + using TextDocumentPositionParams::TextDocumentPositionParams; + + class ReferenceContext : public JsonObject + { + public: + explicit ReferenceContext(bool includeDeclaration) + { setIncludeDeclaration(includeDeclaration); } + ReferenceContext() = default; + using JsonObject::JsonObject; + bool includeDeclaration() const { return typedValue<bool>(includeDeclarationKey); } + void setIncludeDeclaration(bool includeDeclaration) + { insert(includeDeclarationKey, includeDeclaration); } + + bool isValid() const override { return contains(includeDeclarationKey); } + }; + + ReferenceContext context() const { return typedValue<ReferenceContext>(contextKey); } + void setContext(const ReferenceContext &context) { insert(contextKey, context); } + + bool isValid() const override + { return TextDocumentPositionParams::isValid() && contains(contextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT FindReferencesRequest : public Request< + LanguageClientArray<Location>, std::nullptr_t, ReferenceParams> +{ +public: + explicit FindReferencesRequest(const ReferenceParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/references"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlight : public JsonObject +{ +public: + using JsonObject::JsonObject; + + enum DocumentHighlightKind { + Text = 1, + Read = 2, + Write = 3 + }; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + std::optional<int> kind() const { return optionalValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + void clearKind() { remove(kindKey); } + + bool isValid() const override { return contains(rangeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsResult + : public std::variant<QList<DocumentHighlight>, std::nullptr_t> +{ +public: + using variant::variant; + DocumentHighlightsResult() : variant(nullptr) {} + explicit DocumentHighlightsResult(const QJsonValue &value); + using variant::operator=; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsRequest : public Request< + DocumentHighlightsResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit DocumentHighlightsRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentHighlight"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentParams : public JsonObject +{ +public: + TextDocumentParams(); + explicit TextDocumentParams(const TextDocumentIdentifier &identifier); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + bool isValid() const override { return contains(textDocumentKey); } +}; + +using DocumentSymbolParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsResult + : public std::variant<QList<SymbolInformation>, QList<DocumentSymbol>, std::nullptr_t> +{ +public: + using variant::variant; + DocumentSymbolsResult() : variant(nullptr) {} + explicit DocumentSymbolsResult(const QJsonValue &value); + DocumentSymbolsResult(const DocumentSymbolsResult &other) : variant(other) {} + DocumentSymbolsResult(DocumentSymbolsResult &&other) : variant(std::move(other)) {} + + using variant::operator=; + DocumentSymbolsResult &operator =(DocumentSymbolsResult &&other) + { + variant::operator=(std::move(other)); + return *this; + } + DocumentSymbolsResult &operator =(const DocumentSymbolsResult &other) + { + variant::operator=(other); + return *this; + } + // Needed to make it usable in Qt 6 signals. + bool operator<(const DocumentSymbolsResult &other) const = delete; +}; + + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsRequest + : public Request<DocumentSymbolsResult, std::nullptr_t, DocumentSymbolParams> +{ +public: + explicit DocumentSymbolsRequest(const DocumentSymbolParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentSymbol"; +}; + +/** + * The kind of a code action. + * + * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`. + * + * The set of kinds is open and client needs to announce the kinds it supports to the server during + * initialization. + */ + +using CodeActionKind = QString; + +/** + * A set of predefined code action kinds + */ + +namespace CodeActionKinds { +constexpr char QuickFix[] = "quickfix"; +constexpr char Refactor[] = "refactor"; +constexpr char RefactorExtract[] = "refactor.extract"; +constexpr char RefactorInline[] = "refactor.inline"; +constexpr char RefactorRewrite[] = "refactor.rewrite"; +constexpr char Source[] = "source"; +constexpr char SourceOrganizeImports[] = "source.organizeImports"; +} + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT CodeActionContext : public JsonObject + { + public: + using JsonObject::JsonObject; + + QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); } + void setDiagnostics(const QList<Diagnostic> &diagnostics) + { insertArray(diagnosticsKey, diagnostics); } + + std::optional<QList<CodeActionKind>> only() const; + void setOnly(const QList<CodeActionKind> &only); + void clearOnly() { remove(onlyKey); } + + bool isValid() const override { return contains(diagnosticsKey); } + }; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + CodeActionContext context() const { return typedValue<CodeActionContext>(contextKey); } + void setContext(const CodeActionContext &context) { insert(contextKey, context); } + + bool isValid() const override + { return contains(textDocumentKey) && contains(rangeKey) && contains(contextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeAction : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(QString title) { insert(titleKey, title); } + + std::optional<CodeActionKind> kind() const { return optionalValue<CodeActionKind>(kindKey); } + void setKind(const CodeActionKind &kind) { insert(kindKey, kind); } + void clearKind() { remove(kindKey); } + + std::optional<QList<Diagnostic>> diagnostics() const + { return optionalArray<Diagnostic>(diagnosticsKey); } + void setDiagnostics(const QList<Diagnostic> &diagnostics) + { insertArray(diagnosticsKey, diagnostics); } + void clearDiagnostics() { remove(diagnosticsKey); } + + std::optional<WorkspaceEdit> edit() const { return optionalValue<WorkspaceEdit>(editKey); } + void setEdit(const WorkspaceEdit &edit) { insert(editKey, edit); } + void clearEdit() { remove(editKey); } + + std::optional<Command> command() const { return optionalValue<Command>(commandKey); } + void setCommand(const Command &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + bool isValid() const override { return contains(titleKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionResult + : public std::variant<QList<std::variant<Command, CodeAction>>, std::nullptr_t> +{ +public: + using variant::variant; + explicit CodeActionResult(const QJsonValue &val); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionRequest : public Request< + CodeActionResult, std::nullptr_t, CodeActionParams> +{ +public: + explicit CodeActionRequest(const CodeActionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/codeAction"; +}; + +using CodeLensParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLens : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + std::optional<Command> command() const { return optionalValue<Command>(commandKey); } + void setCommand(const Command &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + std::optional<QJsonValue> data() const; + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid() const override { return contains(rangeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLensRequest : public Request< + LanguageClientArray<CodeLens>, std::nullptr_t, CodeLensParams> +{ +public: + explicit CodeLensRequest(const CodeLensParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/codeLens"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLensResolveRequest : public Request< + CodeLens, std::nullptr_t, CodeLens> +{ +public: + explicit CodeLensResolveRequest(const CodeLens ¶ms); + using Request::Request; + constexpr static const char methodName[] = "codeLens/resolve"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLink : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + std::optional<DocumentUri> target() const; + void setTarget(const DocumentUri &target) { insert(targetKey, target.toString()); } + void clearTarget() { remove(targetKey); } + + std::optional<QJsonValue> data() const; + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid() const override { return contains(rangeKey); } +}; + +using DocumentLinkParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkRequest : public Request< + LanguageClientValue<DocumentLink>, std::nullptr_t, DocumentLinkParams> +{ +public: + explicit DocumentLinkRequest(const DocumentLinkParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentLink"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkResolveRequest : public Request< + DocumentLink, std::nullptr_t, DocumentLink> +{ +public: + explicit DocumentLinkResolveRequest(const DocumentLink ¶ms); + using Request::Request; + constexpr static const char methodName[] = "documentLink/resolve"; +}; + +using DocumentColorParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT Color : public JsonObject +{ +public: + using JsonObject::JsonObject; + + double red() const { return typedValue<double>(redKey); } + void setRed(double red) { insert(redKey, red); } + + double green() const { return typedValue<double>(greenKey); } + void setGreen(double green) { insert(greenKey, green); } + + double blue() const { return typedValue<double>(blueKey); } + void setBlue(double blue) { insert(blueKey, blue); } + + double alpha() const { return typedValue<double>(alphaKey); } + void setAlpha(double alpha) { insert(alphaKey, alpha); } + + bool isValid() const override + { return contains(redKey) && contains(greenKey) && contains(blueKey) && contains(alphaKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(Range range) { insert(rangeKey, range); } + + Color color() const { return typedValue<Color>(colorKey); } + void setColor(const Color &color) { insert(colorKey, color); } + + bool isValid() const override { return contains(rangeKey) && contains(colorKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentColorRequest : public Request< + QList<ColorInformation>, std::nullptr_t, DocumentColorParams> +{ +public: + explicit DocumentColorRequest(const DocumentColorParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentColor"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Color colorInfo() const { return typedValue<Color>(colorInfoKey); } + void setColorInfo(const Color &colorInfo) { insert(colorInfoKey, colorInfo); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + bool isValid() const override + { return contains(textDocumentKey) && contains(colorInfoKey) && contains(rangeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + std::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); } + void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); } + void clearTextEdit() { remove(textEditKey); } + + std::optional<QList<TextEdit>> additionalTextEdits() const + { return optionalArray<TextEdit>(additionalTextEditsKey); } + void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits) + { insertArray(additionalTextEditsKey, additionalTextEdits); } + void clearAdditionalTextEdits() { remove(additionalTextEditsKey); } + + bool isValid() const override { return contains(labelKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationRequest : public Request< + QList<ColorPresentation>, std::nullptr_t, ColorPresentationParams> +{ +public: + explicit ColorPresentationRequest(const ColorPresentationParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/colorPresentation"; +}; + +class DocumentFormattingProperty : public std::variant<bool, double, QString> +{ +public: + DocumentFormattingProperty() = default; + explicit DocumentFormattingProperty(const QJsonValue &value); + explicit DocumentFormattingProperty(const DocumentFormattingProperty &other) + : std::variant<bool, double, QString>(other) {} + + using variant::variant; + using variant::operator=; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT FormattingOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int tabSize() const { return typedValue<int>(tabSizeKey); } + void setTabSize(int tabSize) { insert(tabSizeKey, tabSize); } + + bool insertSpace() const { return typedValue<bool>(insertSpaceKey); } + void setInsertSpace(bool insertSpace) { insert(insertSpaceKey, insertSpace); } + + std::optional<bool> trimTrailingWhitespace() const + { return optionalValue<bool>(trimTrailingWhitespaceKey); } + void setTrimTrailingWhitespace(bool trimTrailingWhitespace) + { insert(trimTrailingWhitespaceKey, trimTrailingWhitespace); } + void clearTrimTrailingWhitespace() { remove(trimTrailingWhitespaceKey); } + + std::optional<bool> insertFinalNewline() const + { return optionalValue<bool>(insertFinalNewlineKey); } + void setInsertFinalNewline(bool insertFinalNewline) + { insert(insertFinalNewlineKey, insertFinalNewline); } + void clearInsertFinalNewline() { remove(insertFinalNewlineKey); } + + std::optional<bool> trimFinalNewlines() const + { return optionalValue<bool>(trimFinalNewlinesKey); } + void setTrimFinalNewlines(bool trimFinalNewlines) + { insert(trimFinalNewlinesKey, trimFinalNewlines); } + void clearTrimFinalNewlines() { remove(trimFinalNewlinesKey); } + + QHash<QString, DocumentFormattingProperty> properties() const; + void setProperty(const std::string_view key, const DocumentFormattingProperty &property); + void removeProperty(const std::string_view &key) { remove(key); } + + bool isValid() const override { return contains(insertSpaceKey) && contains(tabSizeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid() const override { return contains(textDocumentKey) && contains(optionsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingRequest : public Request< + LanguageClientArray<TextEdit>, std::nullptr_t, DocumentFormattingParams> +{ +public: + explicit DocumentFormattingRequest(const DocumentFormattingParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/formatting"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid() const override + { return contains(textDocumentKey) && contains(rangeKey) && contains(optionsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingRequest : public Request< + LanguageClientArray<TextEdit>, std::nullptr_t, DocumentRangeFormattingParams> +{ +public: + explicit DocumentRangeFormattingRequest(const DocumentRangeFormattingParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/rangeFormatting"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + QString ch() const { return typedValue<QString>(chKey); } + void setCh(const QString &ch) { insert(chKey, ch); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingRequest : public Request< + LanguageClientArray<TextEdit>, std::nullptr_t, DocumentOnTypeFormattingParams> +{ +public: + explicit DocumentOnTypeFormattingRequest(const DocumentOnTypeFormattingParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/onTypeFormatting"; +}; + +class PlaceHolderResult : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + QString placeHolder() const { return typedValue<QString>(placeHolderKey); } + void setPlaceHolder(const QString &placeHolder) { insert(placeHolderKey, placeHolder); } + + bool isValid() const override { return contains(rangeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT PrepareRenameResult + : public std::variant<PlaceHolderResult, Range, std::nullptr_t> +{ +public: + PrepareRenameResult(); + PrepareRenameResult(const std::variant<PlaceHolderResult, Range, std::nullptr_t> &val); + explicit PrepareRenameResult(const PlaceHolderResult &val); + explicit PrepareRenameResult(const Range &val); + explicit PrepareRenameResult(const QJsonValue &val); + + bool isValid() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT PrepareRenameRequest + : public Request<PrepareRenameResult, std::nullptr_t, TextDocumentPositionParams> +{ +public: + explicit PrepareRenameRequest(const TextDocumentPositionParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/prepareRename"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RenameParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + QString newName() const { return typedValue<QString>(newNameKey); } + void setNewName(const QString &newName) { insert(newNameKey, newName); } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RenameRequest : public Request< + WorkspaceEdit, std::nullptr_t, RenameParams> +{ +public: + explicit RenameRequest(const RenameParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/rename"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/languageserverprotocol_global.h b/src/shared/lsp/languageserverprotocol_global.h new file mode 100644 index 000000000..7f06f1216 --- /dev/null +++ b/src/shared/lsp/languageserverprotocol_global.h @@ -0,0 +1,14 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QtGlobal> + +#if defined(LANGUAGESERVERPROTOCOL_LIBRARY) +# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_EXPORT +#elif defined(LANGUAGESERVERPROTOCOL_STATIC_LIBRARY) +# define LANGUAGESERVERPROTOCOL_EXPORT +#else +# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/shared/lsp/languageserverprotocoltr.h b/src/shared/lsp/languageserverprotocoltr.h new file mode 100644 index 000000000..a4e2f324c --- /dev/null +++ b/src/shared/lsp/languageserverprotocoltr.h @@ -0,0 +1,15 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QCoreApplication> + +namespace lsp { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::LanguageServerProtocol) +}; + +} // LanguageServerProtocol diff --git a/src/shared/lsp/link.cpp b/src/shared/lsp/link.cpp new file mode 100644 index 000000000..e25b828d7 --- /dev/null +++ b/src/shared/lsp/link.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "link.h" + +#include "textutils.h" + +namespace lsp::Utils { + +/*! + Returns the Link to \a filePath. + If \a canContainLineNumber is true the line number, and column number components + are extracted from the \a filePath's \c path() and the found \a postfix is set. + + The following patterns are supported: \c {filepath.txt:19}, + \c{filepath.txt:19:12}, \c {filepath.txt+19}, + \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. +*/ + +Link Link::fromString(const QString &filePathWithNumbers, bool canContainLineNumber) +{ + Link link; + if (!canContainLineNumber) { + link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers); + } else { + int postfixPos = -1; + const Text::Position pos = Text::Position::fromFileName(filePathWithNumbers, postfixPos); + link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers.left(postfixPos)); + link.targetLine = pos.line; + link.targetColumn = pos.column; + } + return link; +} + +} // namespace lsp::Utils diff --git a/src/shared/lsp/link.h b/src/shared/lsp/link.h new file mode 100644 index 000000000..eaafe280c --- /dev/null +++ b/src/shared/lsp/link.h @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "filepath.h" + +#include <QMetaType> +#include <QMultiHash> +#include <QString> + +#include <functional> + +namespace lsp::Utils { + +class Link +{ +public: + Link() = default; + Link(const FilePath &filePath, int line = 0, int column = 0) + : targetFilePath(filePath) + , targetLine(line) + , targetColumn(column) + {} + + static Link fromString(const QString &filePathWithNumbers, bool canContainLineNumber = false); + + bool hasValidTarget() const + { + if (!targetFilePath.isEmpty()) + return true; + return !targetFilePath.scheme().isEmpty() || !targetFilePath.host().isEmpty(); + } + + bool hasValidLinkText() const + { return linkTextStart != linkTextEnd; } + + bool operator==(const Link &other) const + { + return hasSameLocation(other) + && linkTextStart == other.linkTextStart + && linkTextEnd == other.linkTextEnd; + } + bool operator!=(const Link &other) const { return !(*this == other); } + + bool hasSameLocation(const Link &other) const + { + return targetFilePath == other.targetFilePath + && targetLine == other.targetLine + && targetColumn == other.targetColumn; + } + + friend size_t qHash(const Link &l, uint seed = 0) + { return QT_PREPEND_NAMESPACE(qHash)(l.targetFilePath, seed) + | QT_PREPEND_NAMESPACE(qHash(l.targetLine, seed)) + | QT_PREPEND_NAMESPACE(qHash(l.targetColumn, seed)); } + + int linkTextStart = -1; + int linkTextEnd = -1; + + FilePath targetFilePath; + int targetLine = 0; + int targetColumn = 0; +}; + +using LinkHandler = std::function<void(const Link &)>; +using Links = QList<Link>; + +} // namespace lsp::Utils + +namespace std { + +template<> struct hash<lsp::Utils::Link> +{ + size_t operator()(const lsp::Utils::Link &fn) const { return qHash(fn); } +}; + +} // std diff --git a/src/shared/lsp/lsp.qbs b/src/shared/lsp/lsp.qbs new file mode 100644 index 000000000..95323d332 --- /dev/null +++ b/src/shared/lsp/lsp.qbs @@ -0,0 +1,72 @@ +import qbs.Utilities + +QbsStaticLibrary { + name: "qtclsp" + + Depends { name: "qbscore"; cpp.link: false } + + Depends { + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 + name: "Qt.core5compat" + } + + cpp.defines: base.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; }) + .concat("LANGUAGESERVERPROTOCOL_STATIC_LIBRARY") + + files: [ + "algorithm.h", + "basemessage.cpp", + "basemessage.h", + "callhierarchy.cpp", + "callhierarchy.h", + "client.cpp", + "client.h", + "clientcapabilities.cpp", + "clientcapabilities.h", + "completion.cpp", + "completion.h", + "diagnostics.cpp", + "diagnostics.h", + "filepath.h", + "initializemessages.cpp", + "initializemessages.h", + "jsonkeys.h", + "jsonobject.cpp", + "jsonobject.h", + "jsonrpcmessages.cpp", + "jsonrpcmessages.h", + "languagefeatures.cpp", + "languagefeatures.h", + "languageserverprotocol_global.h", + "languageserverprotocoltr.h", + "link.cpp", + "link.h", + "lsptypes.cpp", + "lsptypes.h", + "lsputils.cpp", + "lsputils.h", + "messages.cpp", + "messages.h", + "predicates.h", + "progresssupport.cpp", + "progresssupport.h", + "semantictokens.cpp", + "semantictokens.h", + "servercapabilities.cpp", + "servercapabilities.h", + "shutdownmessages.cpp", + "shutdownmessages.h", + "textsynchronization.cpp", + "textsynchronization.h", + "textutils.cpp", + "textutils.h", + "workspace.cpp", + "workspace.h", + ] + + Export { + Depends { name: "cpp" } + cpp.defines: "LANGUAGESERVERPROTOCOL_STATIC_LIBRARY" + cpp.includePaths: exportingProduct.sourceDirectory + "/.." + } +} diff --git a/src/shared/lsp/lsptypes.cpp b/src/shared/lsp/lsptypes.cpp new file mode 100644 index 000000000..751fc4e3d --- /dev/null +++ b/src/shared/lsp/lsptypes.cpp @@ -0,0 +1,364 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "lsptypes.h" + +#include "languageserverprotocoltr.h" +#include "lsputils.h" +#include "textutils.h" + +#include <QFile> +#include <QHash> +#include <QJsonArray> +#include <QMap> +#include <QRegularExpression> +#include <QVector> + +namespace lsp { + +std::optional<DiagnosticSeverity> Diagnostic::severity() const +{ + if (auto val = optionalValue<int>(severityKey)) + return std::make_optional(static_cast<DiagnosticSeverity>(*val)); + return std::nullopt; +} + +std::optional<Diagnostic::Code> Diagnostic::code() const +{ + QJsonValue codeValue = value(codeKey); + auto it = find(codeKey); + if (codeValue.isUndefined()) + return std::nullopt; + QJsonValue::Type type = it.value().type(); + if (type != QJsonValue::String && type != QJsonValue::Double) + return std::make_optional(Code(QString())); + return std::make_optional(codeValue.isDouble() ? Code(codeValue.toInt()) + : Code(codeValue.toString())); +} + +void Diagnostic::setCode(const Diagnostic::Code &code) +{ + insertVariant<int, QString>(codeKey, code); +} + +std::optional<WorkspaceEdit::Changes> WorkspaceEdit::changes() const +{ + auto it = find(changesKey); + if (it == end()) + return std::nullopt; + const QJsonObject &changesObject = it.value().toObject(); + Changes changesResult; + for (const QString &key : changesObject.keys()) + changesResult[DocumentUri::fromProtocol(key)] = LanguageClientArray<TextEdit>(changesObject.value(key)).toList(); + return std::make_optional(changesResult); +} + +void WorkspaceEdit::setChanges(const Changes &changes) +{ + QJsonObject changesObject; + const auto end = changes.end(); + for (auto it = changes.begin(); it != end; ++it) { + QJsonArray edits; + for (const TextEdit &edit : it.value()) + edits.append(QJsonValue(edit)); + changesObject.insert(QJsonValue(it.key()).toString(), edits); + } + insert(changesKey, changesObject); +} + +WorkSpaceFolder::WorkSpaceFolder(const DocumentUri &uri, const QString &name) +{ + setUri(uri); + setName(name); +} + +MarkupOrString::MarkupOrString(const std::variant<QString, MarkupContent> &val) + : std::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const QString &val) + : std::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const MarkupContent &val) + : std::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const QJsonValue &val) +{ + if (val.isString()) { + emplace<QString>(val.toString()); + } else { + MarkupContent markupContent(val.toObject()); + if (markupContent.isValid()) + emplace<MarkupContent>(MarkupContent(val.toObject())); + } +} + +QJsonValue MarkupOrString::toJson() const +{ + if (std::holds_alternative<QString>(*this)) + return std::get<QString>(*this); + if (std::holds_alternative<MarkupContent>(*this)) + return QJsonValue(std::get<MarkupContent>(*this)); + return {}; +} + +QMap<QString, QString> languageIds() +{ + static const QMap<QString, QString> languages({ + {"Windows Bat","bat" }, + {"BibTeX","bibtex" }, + {"Clojure","clojure" }, + {"Coffeescript","coffeescript" }, + {"C","c" }, + {"C++","cpp" }, + {"C#","csharp" }, + {"CSS","css" }, + {"Diff","diff" }, + {"Dockerfile","dockerfile" }, + {"F#","fsharp" }, + {"Git commit","git-commit" }, + {"Git rebase","git-rebase" }, + {"Go","go" }, + {"Groovy","groovy" }, + {"Handlebars","handlebars" }, + {"HTML","html" }, + {"Ini","ini" }, + {"Java","java" }, + {"JavaScript","javascript" }, + {"JSON","json" }, + {"LaTeX","latex" }, + {"Less","less" }, + {"Lua","lua" }, + {"Makefile","makefile" }, + {"Markdown","markdown" }, + {"Objective-C","objective-c" }, + {"Objective-C++","objective-cpp" }, + {"Perl6","perl6" }, + {"Perl","perl" }, + {"PHP","php" }, + {"Powershell","powershell" }, + {"Pug","jade" }, + {"Python","python" }, + {"R","r" }, + {"Razor (cshtml)","razor" }, + {"Ruby","ruby" }, + {"Rust","rust" }, + {"Scss (syntax using curly brackets)","scss"}, + {"Sass (indented syntax)","sass" }, + {"ShaderLab","shaderlab" }, + {"Shell Script (Bash)","shellscript" }, + {"SQL","sql" }, + {"Swift","swift" }, + {"TypeScript","typescript" }, + {"TeX","tex" }, + {"Visual Basic","vb" }, + {"XML","xml" }, + {"XSL","xsl" }, + {"YAML","yaml" } + }); + return languages; +} + +bool TextDocumentItem::isValid() const +{ + return contains(uriKey) && contains(languageIdKey) && contains(versionKey) && contains(textKey); +} + +TextDocumentPositionParams::TextDocumentPositionParams() + : TextDocumentPositionParams(TextDocumentIdentifier(), Position()) +{ + +} + +TextDocumentPositionParams::TextDocumentPositionParams( + const TextDocumentIdentifier &document, const Position &position) +{ + setTextDocument(document); + setPosition(position); +} + +Position::Position(int line, int character) +{ + setLine(line); + setCharacter(character); +} + +Range::Range(const Position &start, const Position &end) +{ + setStart(start); + setEnd(end); +} + +bool Range::contains(const Range &other) const +{ + if (start() > other.start()) + return false; + if (end() < other.end()) + return false; + return true; +} + +bool Range::overlaps(const Range &range) const +{ + return !isLeftOf(range) && !range.isLeftOf(*this); +} + +QString expressionForGlob(QString globPattern) +{ + const QString anySubDir("qtc_anysubdir_id"); + globPattern.replace("**/", anySubDir); + QString regexp = QRegularExpression::wildcardToRegularExpression(globPattern); + regexp.replace(anySubDir,"(.*[/\\\\])*"); + regexp.replace("\\{", "("); + regexp.replace("\\}", ")"); + regexp.replace(",", "|"); + return regexp; +} + +bool DocumentFilter::applies(const Utils::FilePath &fileName) const +{ + if (std::optional<QString> _pattern = pattern()) { + QRegularExpression::PatternOption option = QRegularExpression::NoPatternOption; + if (fileName.caseSensitivity() == Qt::CaseInsensitive) + option = QRegularExpression::CaseInsensitiveOption; + const QRegularExpression regexp(expressionForGlob(*_pattern), option); + if (regexp.isValid() && regexp.match(fileName.toString()).hasMatch()) + return true; + } + // return false when any of the filter didn't match but return true when no filter was defined + return !contains(schemeKey) && !contains(languageKey) && !contains(patternKey); +} + +Utils::Link Location::toLink(const DocumentUri::PathMapper &mapToHostPath) const +{ + if (!isValid()) + return Utils::Link(); + + return Utils::Link(uri().toFilePath(mapToHostPath), + range().start().line() + 1, + range().start().character()); +} + +// Ensure %xx like %20 are really decoded using fromPercentEncoding +// Else, a path with spaces would keep its %20 which would cause failure +// to open the file by the text editor. This is the cases with compilers in +// C:\Programs Files on Windows. +DocumentUri::DocumentUri(const QString &other) + : QUrl(QUrl::fromPercentEncoding(other.toUtf8())) +{ } + +DocumentUri::DocumentUri(const Utils::FilePath &other, const PathMapper &mapToServerPath) + : QUrl(QUrl::fromLocalFile(mapToServerPath(other).path())) +{ } + +Utils::FilePath DocumentUri::toFilePath(const PathMapper &mapToHostPath) const +{ + if (isLocalFile()) { + const Utils::FilePath serverPath = Utils::FilePath::fromUserInput(toLocalFile()); + QBS_ASSERT(mapToHostPath, return serverPath); + return mapToHostPath(serverPath); + } + return {}; +} + +DocumentUri DocumentUri::fromProtocol(const QString &uri) +{ + return DocumentUri(uri); +} + +DocumentUri DocumentUri::fromFilePath(const Utils::FilePath &file, const PathMapper &mapToServerPath) +{ + return DocumentUri(file, mapToServerPath); +} + +MarkupKind::MarkupKind(const QJsonValue &value) +{ + m_value = value.toString() == "markdown" ? markdown : plaintext; +} + +MarkupKind::operator QJsonValue() const +{ + switch (m_value) { + case MarkupKind::markdown: + return "markdown"; + case MarkupKind::plaintext: + return "plaintext"; + } + return {}; +} + +DocumentChange::DocumentChange(const QJsonValue &value) +{ + const QString kind = value["kind"].toString(); + if (kind == "create") + emplace<CreateFileOperation>(value); + else if (kind == "rename") + emplace<RenameFileOperation>(value); + else if (kind == "delete") + emplace<DeleteFileOperation>(value); + else + emplace<TextDocumentEdit>(value); +} + +using DocumentChangeBase = std::variant<TextDocumentEdit, CreateFileOperation, RenameFileOperation, DeleteFileOperation>; + +bool DocumentChange::isValid() const +{ + return std::visit([](const auto &v) { return v.isValid(); }, DocumentChangeBase(*this)); +} + +DocumentChange::operator const QJsonValue () const +{ + return std::visit([](const auto &v) { return QJsonValue(v); }, DocumentChangeBase(*this)); +} + +CreateFileOperation::CreateFileOperation() +{ + insert(kindKey, "create"); +} + +QString CreateFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const +{ + return Tr::tr("Create %1").arg(uri().toFilePath(mapToHostPath).toUserOutput()); +} + +bool CreateFileOperation::isValid() const +{ + return contains(uriKey) && value(kindKey) == "create"; +} + +RenameFileOperation::RenameFileOperation() +{ + insert(kindKey, "rename"); +} + +QString RenameFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const +{ + return Tr::tr("Rename %1 to %2") + .arg(oldUri().toFilePath(mapToHostPath).toUserOutput(), + newUri().toFilePath(mapToHostPath).toUserOutput()); +} + +bool RenameFileOperation::isValid() const +{ + return contains(oldUriKey) && contains(newUriKey) && value(kindKey) == "rename"; +} + +DeleteFileOperation::DeleteFileOperation() +{ + insert(kindKey, "delete"); +} + +QString DeleteFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const +{ + return Tr::tr("Delete %1").arg(uri().toFilePath(mapToHostPath).toUserOutput()); +} + +bool DeleteFileOperation::isValid() const +{ + return contains(uriKey) && value(kindKey) == "delete"; +} + +} // namespace lsp diff --git a/src/shared/lsp/lsptypes.h b/src/shared/lsp/lsptypes.h new file mode 100644 index 000000000..52e7b95a9 --- /dev/null +++ b/src/shared/lsp/lsptypes.h @@ -0,0 +1,680 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonkeys.h" +#include "jsonobject.h" +#include "lsputils.h" +#include "filepath.h" +#include "link.h" + +#include <QJsonObject> +#include <QUrl> +#include <QList> + +#include <optional> +#include <variant> + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentUri : public QUrl +{ +public: + DocumentUri() = default; + using PathMapper = std::function<Utils::FilePath(const Utils::FilePath &)>; + Utils::FilePath toFilePath(const PathMapper &mapToHostPath) const; + + static DocumentUri fromProtocol(const QString &uri); + static DocumentUri fromFilePath(const Utils::FilePath &file, const PathMapper &mapToServerPath); + + operator QJsonValue() const { return QJsonValue(toString()); } + +private: + DocumentUri(const QString &other); + DocumentUri(const Utils::FilePath &other, const PathMapper &mapToServerPath); + + friend class LanguageClientValue<QString>; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Position : public JsonObject +{ +public: + Position() = default; + Position(int line, int character); + using JsonObject::JsonObject; + + // Line position in a document (zero-based). + int line() const { return typedValue<int>(lineKey); } + void setLine(int line) { insert(lineKey, line); } + + /* + * Character offset on a line in a document (zero-based). Assuming that the line is + * represented as a string, the `character` value represents the gap between the + * `character` and `character + 1`. + * + * If the character value is greater than the line length it defaults back to the + * line length. + */ + int character() const { return typedValue<int>(characterKey); } + void setCharacter(int character) { insert(characterKey, character); } + + bool isValid() const override + { return contains(lineKey) && contains(characterKey); } +}; + +inline bool operator<(const Position &first, const Position &second) +{ + return first.line() < second.line() + || (first.line() == second.line() && first.character() < second.character()); +} + +inline bool operator>(const Position &first, const Position &second) +{ + return second < first; +} + +inline bool operator>=(const Position &first, const Position &second) +{ + return !(first < second); +} + +inline bool operator<=(const Position &first, const Position &second) +{ + return !(first > second); +} + +class LANGUAGESERVERPROTOCOL_EXPORT Range : public JsonObject +{ +public: + Range() = default; + Range(const Position &start, const Position &end); + using JsonObject::JsonObject; + + // The range's start position. + Position start() const { return typedValue<Position>(startKey); } + void setStart(const Position &start) { insert(startKey, start); } + + // The range's end position. + Position end() const { return typedValue<Position>(endKey); } + void setEnd(const Position &end) { insert(endKey, end); } + + bool isEmpty() const { return start() == end(); } + bool contains(const Position &pos) const { return start() <= pos && pos <= end(); } + bool contains(const Range &other) const; + bool overlaps(const Range &range) const; + bool isLeftOf(const Range &other) const + { return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); } + + bool isValid() const override + { return JsonObject::contains(startKey) && JsonObject::contains(endKey); } +}; + +inline bool operator==(const Range &r1, const Range &r2) +{ + return r1.contains(r2) && r2.contains(r1); +} +inline bool operator!=(const Range &r1, const Range &r2) { return !(r1 == r2); } + +class LANGUAGESERVERPROTOCOL_EXPORT Location : public JsonObject +{ +public: + using JsonObject::JsonObject; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + Utils::Link toLink(const DocumentUri::PathMapper &mapToHostPath) const; + + bool isValid() const override { return contains(uriKey) && contains(rangeKey); } +}; + +enum class DiagnosticSeverity +{ + Error = 1, + Warning = 2, + Information = 3, + Hint = 4 + +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Diagnostic : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The range at which the message applies. + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + // The diagnostic's severity. Can be omitted. If omitted it is up to the + // client to interpret diagnostics as error, warning, info or hint. + std::optional<DiagnosticSeverity> severity() const; + void setSeverity(const DiagnosticSeverity &severity) + { insert(severityKey, static_cast<int>(severity)); } + void clearSeverity() { remove(severityKey); } + + // The diagnostic's code, which might appear in the user interface. + using Code = std::variant<int, QString>; + std::optional<Code> code() const; + void setCode(const Code &code); + void clearCode() { remove(codeKey); } + + // A human-readable string describing the source of this + // diagnostic, e.g. 'typescript' or 'super lint'. + std::optional<QString> source() const + { return optionalValue<QString>(sourceKey); } + void setSource(const QString &source) { insert(sourceKey, source); } + void clearSource() { remove(sourceKey); } + + // The diagnostic's message. + QString message() const + { return typedValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + + bool isValid() const override { return contains(rangeKey) && contains(messageKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Command : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Title of the command, like `save`. + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(const QString &title) { insert(titleKey, title); } + void clearTitle() { remove(titleKey); } + + // The identifier of the actual command handler. + QString command() const { return typedValue<QString>(commandKey); } + void setCommand(const QString &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + // Arguments that the command handler should be invoked with. + std::optional<QJsonArray> arguments() const { return typedValue<QJsonArray>(argumentsKey); } + void setArguments(const QJsonArray &arguments) { insert(argumentsKey, arguments); } + void clearArguments() { remove(argumentsKey); } + + bool isValid() const override { return contains(titleKey) && contains(commandKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The range of the text document to be manipulated. To insert + // text into a document create a range where start === end. + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + // The string to be inserted. For delete operations use an empty string. + QString newText() const { return typedValue<QString>(newTextKey); } + void setNewText(const QString &text) { insert(newTextKey, text); } + + bool isValid() const override + { return contains(rangeKey) && contains(newTextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentIdentifier : public JsonObject +{ +public: + TextDocumentIdentifier() : TextDocumentIdentifier(DocumentUri()) {} + explicit TextDocumentIdentifier(const DocumentUri &uri) { setUri(uri); } + using JsonObject::JsonObject; + + // The text document's URI. + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + bool isValid() const override { return contains(uriKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT VersionedTextDocumentIdentifier : public TextDocumentIdentifier +{ +public: + using TextDocumentIdentifier::TextDocumentIdentifier; + + /* + * The version number of this document. If a versioned text document identifier + * is sent from the server to the client and the file is not open in the editor + * (the server has not received an open notification before) the server can send + * `null` to indicate that the version is known and the content on disk is the + * truth (as speced with document content ownership) + */ + LanguageClientValue<int> version() const { return clientValue<int>(versionKey); } + void setVersion(LanguageClientValue<int> version) { insert(versionKey, version); } + + bool isValid() const override + { + return TextDocumentIdentifier::isValid() && contains(versionKey); + } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The text document to change. + VersionedTextDocumentIdentifier textDocument() const + { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const VersionedTextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + // The edits to be applied. + QList<TextEdit> edits() const { return array<TextEdit>(editsKey); } + void setEdits(const QList<TextEdit> edits) { insertArray(editsKey, edits); } + + bool isValid() const override { return contains(textDocumentKey) && contains(editsKey); } +}; + +class CreateFileOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<bool> overwrite() const { return optionalValue<bool>(overwriteKey); } + void setOverwrite(bool overwrite) { insert(overwriteKey, overwrite); } + void clearOverwrite() { remove(overwriteKey); } + + std::optional<bool> ignoreIfExists() const { return optionalValue<bool>(ignoreIfExistsKey); } + void setIgnoreIfExists(bool ignoreIfExists) { insert(ignoreIfExistsKey, ignoreIfExists); } + void clearIgnoreIfExists() { remove(ignoreIfExistsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CreateFileOperation : public JsonObject +{ +public: + using JsonObject::JsonObject; + CreateFileOperation(); + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + std::optional<CreateFileOptions> options() const + { return optionalValue<CreateFileOptions>(optionsKey); } + void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); } + void clearOptions() { remove(optionsKey); } + + QString message(const DocumentUri::PathMapper &mapToHostPath) const; + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RenameFileOperation : public JsonObject +{ +public: + using JsonObject::JsonObject; + RenameFileOperation(); + + DocumentUri oldUri() const { return DocumentUri::fromProtocol(typedValue<QString>(oldUriKey)); } + void setOldUri(const DocumentUri &oldUri) { insert(oldUriKey, oldUri); } + + DocumentUri newUri() const { return DocumentUri::fromProtocol(typedValue<QString>(newUriKey)); } + void setNewUri(const DocumentUri &newUri) { insert(newUriKey, newUri); } + + std::optional<CreateFileOptions> options() const + { return optionalValue<CreateFileOptions>(optionsKey); } + void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); } + void clearOptions() { remove(optionsKey); } + + QString message(const DocumentUri::PathMapper &mapToHostPath) const; + + bool isValid() const override; +}; + +class DeleteFileOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<bool> recursive() const { return optionalValue<bool>(recursiveKey); } + void setRecursive(bool recursive) { insert(recursiveKey, recursive); } + void clearRecursive() { remove(recursiveKey); } + + std::optional<bool> ignoreIfNotExists() const { return optionalValue<bool>(ignoreIfNotExistsKey); } + void setIgnoreIfNotExists(bool ignoreIfNotExists) { insert(ignoreIfNotExistsKey, ignoreIfNotExists); } + void clearIgnoreIfNotExists() { remove(ignoreIfNotExistsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DeleteFileOperation : public JsonObject +{ +public: + using JsonObject::JsonObject; + DeleteFileOperation(); + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + std::optional<DeleteFileOptions> options() const + { return optionalValue<DeleteFileOptions>(optionsKey); } + void setOptions(const DeleteFileOptions &options) { insert(optionsKey, options); } + void clearOptions() { remove(optionsKey); } + + QString message(const DocumentUri::PathMapper &mapToHostPath) const; + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentChange + : public std::variant<TextDocumentEdit, CreateFileOperation, RenameFileOperation, DeleteFileOperation> +{ +public: + using variant::variant; + DocumentChange(const QJsonValue &value); + + bool isValid() const; + + operator const QJsonValue() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Holds changes to existing resources. + using Changes = QMap<DocumentUri, QList<TextEdit>>; + std::optional<Changes> changes() const; + void setChanges(const Changes &changes); + + /* + * Depending on the client capability + * `workspace.workspaceEdit.resourceOperations` document changes are either + * an array of `TextDocumentEdit`s to express changes to n different text + * documents where each text document edit addresses a specific version of + * a text document. Or it can contain above `TextDocumentEdit`s mixed with + * create, rename and delete file / folder operations. + * + * Whether a client supports versioned document edits is expressed via + * `workspace.workspaceEdit.documentChanges` client capability. + * + * If a client neither supports `documentChanges` nor + * `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s + * using the `changes` property are supported. + */ + std::optional<QList<DocumentChange>> documentChanges() const + { return optionalArray<DocumentChange>(documentChangesKey); } + void setDocumentChanges(const QList<DocumentChange> &changes) + { insertArray(documentChangesKey, changes); } +}; + +LANGUAGESERVERPROTOCOL_EXPORT QMap<QString, QString> languageIds(); + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The text document's URI. + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + // The text document's language identifier. + QString languageId() const { return typedValue<QString>(languageIdKey); } + void setLanguageId(const QString &id) { insert(languageIdKey, id); } + + // The version number of this document (it will increase after each change, including undo/redo + int version() const { return typedValue<int>(versionKey); } + void setVersion(int version) { insert(versionKey, version); } + + // The content of the opened text document. + QString text() const { return typedValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + + bool isValid() const override; + +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentPositionParams : public JsonObject +{ +public: + TextDocumentPositionParams(); + TextDocumentPositionParams(const TextDocumentIdentifier &document, const Position &position); + using JsonObject::JsonObject; + + // The text document. + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); } + + // The position inside the text document. + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + bool isValid() const override { return contains(textDocumentKey) && contains(positionKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFilter : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // A language id, like `typescript`. + std::optional<QString> language() const { return optionalValue<QString>(languageKey); } + void setLanguage(const QString &language) { insert(languageKey, language); } + void clearLanguage() { remove(languageKey); } + + // A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + std::optional<QString> scheme() const { return optionalValue<QString>(schemeKey); } + void setScheme(const QString &scheme) { insert(schemeKey, scheme); } + void clearScheme() { remove(schemeKey); } + + /** + * A glob pattern, like `*.{ts,js}`. + * + * Glob patterns can have the following syntax: + * - `*` to match one or more characters in a path segment + * - `?` to match on one character in a path segment + * - `**` to match any number of path segments, including none + * - `{}` to group sub patterns into an OR expression. (e.g. `*.{ts,js}` + * matches all TypeScript and JavaScript files) + * - `[]` to declare a range of characters to match in a path segment + * (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + * - `[!...]` to negate a range of characters to match in a path segment + * (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but + * not `example.0`) + */ + std::optional<QString> pattern() const { return optionalValue<QString>(patternKey); } + void setPattern(const QString &pattern) { insert(patternKey, pattern); } + void clearPattern() { remove(patternKey); } + + bool applies(const Utils::FilePath &fileName) const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkupKind +{ +public: + enum Value { plaintext, markdown }; + MarkupKind() = default; + MarkupKind(const Value value) + : m_value(value) + {} + explicit MarkupKind(const QJsonValue &value); + + operator QJsonValue() const; + Value value() const { return m_value; } + + bool operator==(const Value &value) const { return m_value == value; } + + bool isValid() const { return true; } +private: + Value m_value = plaintext; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkupContent : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The type of the Markup + MarkupKind kind() const { return MarkupKind(value(kindKey)); } + void setKind(MarkupKind kind) { insert(kindKey, kind); } + Qt::TextFormat textFormat() const + { return kind() == MarkupKind::markdown ? Qt::MarkdownText : Qt::PlainText; } + + // The content itself + QString content() const { return typedValue<QString>(contentKey); } + void setContent(const QString &content) { insert(contentKey, content); } + + bool isValid() const override + { return contains(kindKey) && contains(contentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkupOrString : public std::variant<QString, MarkupContent> +{ +public: + MarkupOrString() = default; + explicit MarkupOrString(const std::variant<QString, MarkupContent> &val); + explicit MarkupOrString(const QString &val); + explicit MarkupOrString(const MarkupContent &val); + MarkupOrString(const QJsonValue &val); + + bool isValid() const { return true; } + + QJsonValue toJson() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolder : public JsonObject +{ +public: + WorkSpaceFolder() = default; + WorkSpaceFolder(const DocumentUri &uri, const QString &name); + using JsonObject::JsonObject; + + // The associated URI for this workspace folder. + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + // The name of the workspace folder. Defaults to the uri's basename. + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + bool isValid() const override + { return contains(uriKey) && contains(nameKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SymbolInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + int kind() const { return typedValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + + std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); } + void setDeprecated(bool deprecated) { insert(deprecatedKey, deprecated); } + void clearDeprecated() { remove(deprecatedKey); } + + Location location() const { return typedValue<Location>(locationKey); } + void setLocation(const Location &location) { insert(locationKey, location); } + + std::optional<QString> containerName() const + { return optionalValue<QString>(containerNameKey); } + void setContainerName(const QString &containerName) { insert(containerNameKey, containerName); } + void clearContainerName() { remove(containerNameKey); } + + bool isValid() const override + { return contains(nameKey) && contains(kindKey) && contains(locationKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbol : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + std::optional<QString> detail() const { return optionalValue<QString>(detailKey); } + void setDetail(const QString &detail) { insert(detailKey, detail); } + void clearDetail() { remove(detailKey); } + + int kind() const { return typedValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + + std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); } + void setDeprecated(bool deprecated) { insert(deprecatedKey, deprecated); } + void clearDeprecated() { remove(deprecatedKey); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(Range range) { insert(rangeKey, range); } + + Range selectionRange() const { return typedValue<Range>(selectionRangeKey); } + void setSelectionRange(Range selectionRange) { insert(selectionRangeKey, selectionRange); } + + std::optional<QList<DocumentSymbol>> children() const + { return optionalArray<DocumentSymbol>(childrenKey); } + void setChildren(QList<DocumentSymbol> children) { insertArray(childrenKey, children); } + void clearChildren() { remove(childrenKey); } +}; + +enum class SymbolKind { + File = 1, + FirstSymbolKind = File, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, + LastSymbolKind = TypeParameter, +}; + +namespace CompletionItemKind { +enum Kind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 +}; +} // namespace CompletionItemKind + +} // namespace LanguageClient diff --git a/src/shared/lsp/lsputils.cpp b/src/shared/lsp/lsputils.cpp new file mode 100644 index 000000000..f26a8a21c --- /dev/null +++ b/src/shared/lsp/lsputils.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "lsputils.h" + +#include <QHash> +#include <QLoggingCategory> +#include <QVector> + +namespace lsp { + +Q_LOGGING_CATEGORY(conversionLog, "qtc.languageserverprotocol.conversion", QtWarningMsg) + +template<> +QString fromJsonValue<QString>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isString()) + qCDebug(conversionLog) << "Expected String in json value but got: " << value; + return value.toString(); +} + +template<> +int fromJsonValue<int>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isDouble()) + qCDebug(conversionLog) << "Expected double in json value but got: " << value; + return value.toInt(); +} + +template<> +double fromJsonValue<double>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isDouble()) + qCDebug(conversionLog) << "Expected double in json value but got: " << value; + return value.toDouble(); +} + +template<> +bool fromJsonValue<bool>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isBool()) + qCDebug(conversionLog) << "Expected bool in json value but got: " << value; + return value.toBool(); +} + +template<> +QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isArray()) + qCDebug(conversionLog) << "Expected Array in json value but got: " << value; + return value.toArray(); +} + +template<> +QJsonObject fromJsonValue<QJsonObject>(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isObject()) + qCDebug(conversionLog) << "Expected Object in json value but got: " << value; + return value.toObject(); +} + +template<> +QJsonValue fromJsonValue<QJsonValue>(const QJsonValue &value) +{ + return value; +} + +} // namespace lsp diff --git a/src/shared/lsp/lsputils.h b/src/shared/lsp/lsputils.h new file mode 100644 index 000000000..eb7a18342 --- /dev/null +++ b/src/shared/lsp/lsputils.h @@ -0,0 +1,160 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "languageserverprotocol_global.h" + +#include <tools/qbsassert.h> + +#include <QJsonArray> +#include <QJsonObject> +#include <QLoggingCategory> + +#include <variant> + +namespace lsp { + +LANGUAGESERVERPROTOCOL_EXPORT Q_DECLARE_LOGGING_CATEGORY(conversionLog) + +template <typename T> +T fromJsonValue(const QJsonValue &value) +{ + if (conversionLog().isDebugEnabled() && !value.isObject()) + qCDebug(conversionLog) << "Expected Object in json value but got: " << value; + T result(value.toObject()); + if (conversionLog().isDebugEnabled() && !result.isValid()) + qCDebug(conversionLog) << typeid(result).name() << " is not valid: " << result; + return result; +} + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QString fromJsonValue<QString>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT int fromJsonValue<int>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT double fromJsonValue<double>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT bool fromJsonValue<bool>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QJsonObject fromJsonValue<QJsonObject>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QJsonValue fromJsonValue<QJsonValue>(const QJsonValue &value); + +template <typename T> +class LanguageClientArray : public std::variant<QList<T>, std::nullptr_t> +{ +public: + using std::variant<QList<T>, std::nullptr_t>::variant; + using std::variant<QList<T>, std::nullptr_t>::operator=; + + LanguageClientArray() {} + + explicit LanguageClientArray(const QList<T> &list) + { *this = list; } + + explicit LanguageClientArray(const QJsonValue &value) + { + if (value.isArray()) { + QList<T> values; + values.reserve(value.toArray().count()); + for (auto arrayValue : value.toArray()) + values << fromJsonValue<T>(arrayValue); + *this = values; + } else { + *this = nullptr; + } + } + + QJsonValue toJson() const + { + if (const auto list = std::get_if<QList<T>>(this)) { + QJsonArray array; + for (const T &value : *list) + array.append(QJsonValue(value)); + return array; + } + return QJsonValue(); + } + + QList<T> toListOrEmpty() const + { + if (std::holds_alternative<QList<T>>(*this)) + return std::get<QList<T>>(*this); + return {}; + } + + QList<T> toList() const + { + QBS_ASSERT(std::holds_alternative<QList<T>>(*this), return {}); + return std::get<QList<T>>(*this); + } + bool isNull() const { return std::holds_alternative<std::nullptr_t>(*this); } +}; + +template <typename T> +class LanguageClientValue : public std::variant<T, std::nullptr_t> +{ +public: + using std::variant<T, std::nullptr_t>::operator=; + + LanguageClientValue() : std::variant<T, std::nullptr_t>(nullptr) { } + LanguageClientValue(const T &value) : std::variant<T, std::nullptr_t>(value) { } + LanguageClientValue(const QJsonValue &value) + { + if (!QBS_GUARD(!value.isUndefined()) || value.isNull()) + *this = nullptr; + else + *this = fromJsonValue<T>(value); + } + + operator const QJsonValue() const + { + if (auto val = std::get_if<T>(this)) + return QJsonValue(*val); + return QJsonValue(); + } + + T value(const T &defaultValue = T()) const + { + QBS_ASSERT(std::holds_alternative<T>(*this), return defaultValue); + return std::get<T>(*this); + } + + template<typename Type> + LanguageClientValue<Type> transform() + { + QBS_ASSERT(!std::holds_alternative<T>(*this), return LanguageClientValue<Type>()); + return Type(std::get<T>(*this)); + } + + bool isNull() const { return std::holds_alternative<std::nullptr_t>(*this); } +}; + +template <typename T> +QJsonArray enumArrayToJsonArray(const QList<T> &values) +{ + QJsonArray array; + for (T value : values) + array.append(static_cast<int>(value)); + return array; +} + +template <typename T> +QList<T> jsonArrayToList(const QJsonArray &array) +{ + QList<T> list; + for (const QJsonValue &val : array) + list << fromJsonValue<T>(val); + return list; +} + +} // namespace lsp diff --git a/src/shared/lsp/messages.cpp b/src/shared/lsp/messages.cpp new file mode 100644 index 000000000..a909fdb82 --- /dev/null +++ b/src/shared/lsp/messages.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "messages.h" + +namespace lsp { + +constexpr const char ShowMessageNotification::methodName[]; +constexpr const char ShowMessageRequest::methodName[]; +constexpr const char LogMessageNotification::methodName[]; +constexpr const char TelemetryNotification::methodName[]; + +ShowMessageNotification::ShowMessageNotification(const ShowMessageParams ¶ms) + : Notification(methodName, params) +{ } + +ShowMessageRequest::ShowMessageRequest(const ShowMessageRequestParams ¶ms) + : Request(methodName, params) +{ } + +LogMessageNotification::LogMessageNotification(const LogMessageParams ¶ms) + : Notification(methodName, params) +{ } + +TelemetryNotification::TelemetryNotification(const JsonObject ¶ms) + : Notification(methodName, params) +{ } + +static QString messageTypeName(int messageType) +{ + switch (messageType) { + case Error: return QString("Error"); + case Warning: return QString("Warning"); + case Info: return QString("Info"); + case Log: return QString("Log"); + } + return QString(""); +} + +QString ShowMessageParams::toString() const +{ + return messageTypeName(type()) + ": " + message(); +} + +} // namespace lsp diff --git a/src/shared/lsp/messages.h b/src/shared/lsp/messages.h new file mode 100644 index 000000000..8c7d05c6d --- /dev/null +++ b/src/shared/lsp/messages.h @@ -0,0 +1,93 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +enum MessageType { + Error = 1, + Warning = 2, + Info = 3, + Log = 4, +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int type() const { return typedValue<int>(typeKey); } + void setType(int type) { insert(typeKey, type); } + + QString message() const { return typedValue<QString>(messageKey); } + void setMessage(QString message) { insert(messageKey, message); } + + QString toString() const; + + bool isValid() const override + { return contains(typeKey) && contains(messageKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageNotification : public Notification<ShowMessageParams> +{ +public: + explicit ShowMessageNotification(const ShowMessageParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "window/showMessage"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MessageActionItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(QString title) { insert(titleKey, title); } + + bool isValid() const override { return contains(titleKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequestParams : public ShowMessageParams +{ +public: + using ShowMessageParams::ShowMessageParams; + + std::optional<QList<MessageActionItem>> actions() const + { return optionalArray<MessageActionItem>(actionsKey); } + void setActions(const QList<MessageActionItem> &actions) { insertArray(actionsKey, actions); } + void clearActions() { remove(actionsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequest : public Request< + LanguageClientValue<MessageActionItem>, std::nullptr_t, ShowMessageRequestParams> +{ +public: + explicit ShowMessageRequest(const ShowMessageRequestParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "window/showMessageRequest"; +}; + +using LogMessageParams = ShowMessageParams; + +class LANGUAGESERVERPROTOCOL_EXPORT LogMessageNotification : public Notification<LogMessageParams> +{ +public: + explicit LogMessageNotification(const LogMessageParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "window/logMessage"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TelemetryNotification : public Notification<JsonObject> +{ +public: + explicit TelemetryNotification(const JsonObject ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "telemetry/event"; + + bool parametersAreValid(QString * /*error*/) const override { return params().has_value(); } +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/predicates.h b/src/shared/lsp/predicates.h new file mode 100644 index 000000000..800a84757 --- /dev/null +++ b/src/shared/lsp/predicates.h @@ -0,0 +1,110 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <functional> +#include <type_traits> +#include <utility> + +namespace lsp::Utils +{ + +////////////////// +// find helpers +////////////////// +template<typename R, typename S, typename T> +decltype(auto) equal(R (S::*function)() const, T value) +{ + // This should use std::equal_to<> instead of std::equal_to<T>, + // but that's not supported everywhere yet, since it is C++14 + return std::bind<bool>(std::equal_to<T>(), value, std::bind(function, std::placeholders::_1)); +} + +template<typename R, typename S, typename T> +decltype(auto) equal(R S::*member, T value) +{ + return std::bind<bool>(std::equal_to<T>(), value, std::bind(member, std::placeholders::_1)); +} + +////////////////// +// comparison predicates +////////////////// +template <typename Type> +auto equalTo(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of equalTo should be the same to prevent " + "unnecessary conversion."); + return entry == value; + }; +} + +template <typename Type> +auto unequalTo(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of unequalTo should be the same to prevent " + "unnecessary conversion."); + return !(entry == value); + }; +} + +template <typename Type> +auto lessThan(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of unequalTo should be the same to prevent " + "unnecessary conversion."); + return entry < value; + }; +} + +template <typename Type> +auto lessEqualThan(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of lessEqualThan should be the same to " + "prevent unnecessary conversion."); + return !(value < entry); + }; +} + +template <typename Type> +auto greaterThan(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of greaterThan should be the same to " + "prevent unnecessary conversion."); + return value < entry; + }; +} + +template <typename Type> +auto greaterEqualThan(Type &&value) +{ + return [value = std::forward<Type>(value)] (const auto &entry) + { + static_assert(std::is_same<std::decay_t<Type>, + std::decay_t<decltype(entry)>>::value, + "The container and predicate type of greaterEqualThan should be the same to " + "prevent unnecessary conversion."); + return !(entry < value); + }; +} +} // namespace Utils diff --git a/src/shared/lsp/progresssupport.cpp b/src/shared/lsp/progresssupport.cpp new file mode 100644 index 000000000..6a554b4f4 --- /dev/null +++ b/src/shared/lsp/progresssupport.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "progresssupport.h" + +#include <QUuid> + +namespace lsp { + +ProgressToken::ProgressToken(const QJsonValue &value) +{ + if (!QBS_GUARD(value.isDouble() || value.isString())) + emplace<QString>(QUuid::createUuid().toString()); + else if (value.isDouble()) + emplace<int>(value.toInt()); + else + emplace<QString>(value.toString()); +} + +ProgressToken::operator QJsonValue() const +{ + if (std::holds_alternative<QString>(*this)) + return QJsonValue(std::get<QString>(*this)); + return QJsonValue(std::get<int>(*this)); +} + +ProgressParams::ProgressType ProgressParams::value() const +{ + QJsonObject paramsValue = JsonObject::value(valueKey).toObject(); + if (paramsValue[kindKey] == "begin") + return ProgressParams::ProgressType(WorkDoneProgressBegin(paramsValue)); + if (paramsValue[kindKey] == "report") + return ProgressParams::ProgressType(WorkDoneProgressReport(paramsValue)); + return ProgressParams::ProgressType(WorkDoneProgressEnd(paramsValue)); +} + +void ProgressParams::setValue(const ProgressParams::ProgressType &value) +{ + insertVariant<WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd>(valueKey, value); +} + +ProgressNotification::ProgressNotification(const ProgressParams ¶ms) + : Notification(methodName, params) +{ + +} + +WorkDoneProgressCreateRequest::WorkDoneProgressCreateRequest(const WorkDoneProgressCreateParams ¶ms) + : Request(methodName, params) +{ + +} + +} // namespace lsp diff --git a/src/shared/lsp/progresssupport.h b/src/shared/lsp/progresssupport.h new file mode 100644 index 000000000..8ccc34c37 --- /dev/null +++ b/src/shared/lsp/progresssupport.h @@ -0,0 +1,152 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +#include <QJsonValue> + +#include <variant> + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT ProgressToken : public std::variant<int, QString> +{ +public: + using variant::variant; + explicit ProgressToken(const QJsonValue &value); + + bool isValid() { return true; } + operator QJsonValue() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressReport : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * Controls if a cancel button should be shown to allow the user to cancel the + * long running operation. + * Clients that don't support cancellation can ignore the setting. + */ + std::optional<bool> cancellable() const { return optionalValue<bool>(cancellableKey); } + void setCancellable(bool cancellable) { insert(cancellableKey, cancellable); } + void clearCancellable() { remove(cancellableKey); } + + /** + * Optional, more detailed associated progress message. Contains + * complementary information to the `title`. + * + * Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + * If unset, the previous progress message (if any) is still valid. + */ + std::optional<QString> message() const { return optionalValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + void clearMessage() { remove(messageKey); } + + /** + * Optional progress percentage to display (value 100 is considered 100%). + * If not provided infinite progress is assumed and clients are allowed + * to ignore the `percentage` value in subsequent in report notifications. + * + * The value should be steadily rising. Clients are free to ignore values + * that are not following this rule. + */ + + // Allthough percentage is defined as an uint by the protocol some server + // return a double here. Be less strict and also use a double. + // CAUTION: the range is still 0 - 100 and not 0 - 1 + std::optional<double> percentage() const { return optionalValue<double>(percentageKey); } + void setPercentage(double percentage) { insert(percentageKey, percentage); } + void clearPercentage() { remove(percentageKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressBegin : public WorkDoneProgressReport +{ +public: + using WorkDoneProgressReport::WorkDoneProgressReport; + + /** + * Mandatory title of the progress operation. Used to briefly inform about + * the kind of operation being performed. + * + * Examples: "Indexing" or "Linking dependencies". + */ + + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(const QString &title) { insert(titleKey, title); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressEnd : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * Optional, a final message indicating to for example indicate the outcome + * of the operation. + */ + std::optional<QString> message() const { return optionalValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + void clearMessage() { remove(messageKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ProgressParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + ProgressToken token() const { return ProgressToken(JsonObject::value(tokenKey)); } + void setToken(const ProgressToken &token) { insert(tokenKey, token); } + + using ProgressType + = std::variant<WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd>; + ProgressType value() const; + void setValue(const ProgressType &value); + + bool isValid() const override { return contains(tokenKey) && contains(valueKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ProgressNotification : public Notification<ProgressParams> +{ +public: + ProgressNotification(const ProgressParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "$/progress"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ProgressTokenParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The token to be used to report progress. + ProgressToken token() const { return typedValue<ProgressToken>(tokenKey); } + void setToken(const ProgressToken &token) { insert(tokenKey, token); } +}; + +using WorkDoneProgressCreateParams = ProgressTokenParams; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressCreateRequest + : public Request<std::nullptr_t, std::nullptr_t, WorkDoneProgressCreateParams> +{ +public: + WorkDoneProgressCreateRequest(const WorkDoneProgressCreateParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "window/workDoneProgress/create"; +}; + +using WorkDoneProgressCancelParams = ProgressTokenParams; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressCancelRequest + : public Request<std::nullptr_t, std::nullptr_t, WorkDoneProgressCancelParams> +{ +public: + using Request::Request; + constexpr static const char methodName[] = "window/workDoneProgress/cancel"; +}; + + +} // namespace lsp diff --git a/src/shared/lsp/semantictokens.cpp b/src/shared/lsp/semantictokens.cpp new file mode 100644 index 000000000..209966dcf --- /dev/null +++ b/src/shared/lsp/semantictokens.cpp @@ -0,0 +1,141 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "semantictokens.h" + +namespace lsp { + +bool SemanticTokensLegend::isValid() const +{ + return contains(tokenTypesKey) && contains(tokenModifiersKey); +} + +QMap<QString, int> SemanticTokens::defaultTokenTypesMap() +{ + QMap<QString, int> map; + map.insert("namespace", namespaceToken); + map.insert("type", typeToken); + map.insert("class", classToken); + map.insert("enum", enumToken); + map.insert("interface", interfaceToken); + map.insert("struct", structToken); + map.insert("typeParameter", typeParameterToken); + map.insert("parameter", parameterToken); + map.insert("variable", variableToken); + map.insert("property", propertyToken); + map.insert("enumMember", enumMemberToken); + map.insert("event", eventToken); + map.insert("function", functionToken); + map.insert("method", methodToken); + map.insert("macro", macroToken); + map.insert("keyword", keywordToken); + map.insert("modifier", modifierToken); + map.insert("comment", commentToken); + map.insert("string", stringToken); + map.insert("number", numberToken); + map.insert("regexp", regexpToken); + map.insert("operator", operatorToken); + return map; +} + +QMap<QString, int> SemanticTokens::defaultTokenModifiersMap() +{ + QMap<QString, int> map; + map.insert("declaration", declarationModifier); + map.insert("definition", definitionModifier); + map.insert("readonly", readonlyModifier); + map.insert("static", staticModifier); + map.insert("deprecated", deprecatedModifier); + map.insert("abstract", abstractModifier); + map.insert("async", asyncModifier); + map.insert("modification", modificationModifier); + map.insert("documentation", documentationModifier); + map.insert("defaultLibrary", defaultLibraryModifier); + return map; +} + +static int convertModifiers(int modifiersData, const QList<int> &tokenModifiers) +{ + int result = 0; + for (int i = 0; i < tokenModifiers.size() && modifiersData > 0; ++i) { + if (modifiersData & 0x1) { + const int modifier = tokenModifiers[i]; + if (modifier > 0) + result |= modifier; + } + modifiersData = modifiersData >> 1; + } + return result; +} + +QList<SemanticToken> SemanticTokens::toTokens(const QList<int> &tokenTypes, + const QList<int> &tokenModifiers) const +{ + const QList<int> &data = this->data(); + if (data.size() % 5 != 0) + return {}; + QList<SemanticToken> tokens; + tokens.reserve(int(data.size() / 5)); + auto end = data.end(); + for (auto it = data.begin(); it != end; it += 5) { + SemanticToken token; + token.deltaLine = *(it); + token.deltaStart = *(it + 1); + token.length = *(it + 2); + token.tokenIndex = *(it + 3); + token.tokenType = tokenTypes.value(token.tokenIndex, -1); + token.rawTokenModifiers = *(it + 4); + token.tokenModifiers = convertModifiers(token.rawTokenModifiers, tokenModifiers); + tokens << token; + } + return tokens; +} + +bool SemanticTokensRangeParams::isValid() const +{ + return SemanticTokensParams::isValid() && contains(rangeKey); +} + +SemanticTokensFullRequest::SemanticTokensFullRequest(const SemanticTokensParams ¶ms) + : Request(methodName, params) +{} + +SemanticTokensRangeRequest::SemanticTokensRangeRequest(const SemanticTokensRangeParams ¶ms) + : Request(methodName, params) +{} + +SemanticTokensResult::SemanticTokensResult(const QJsonValue &value) +{ + if (value.isObject()) + emplace<SemanticTokens>(SemanticTokens(value.toObject())); + else + emplace<std::nullptr_t>(nullptr); +} + +SemanticTokensFullDeltaRequest::SemanticTokensFullDeltaRequest(const SemanticTokensDeltaParams ¶ms) + : Request(methodName, params) +{} + +bool SemanticTokensDeltaParams::isValid() const +{ + return SemanticTokensParams::isValid() && contains(previousResultIdKey); +} + +SemanticTokensDeltaResult::SemanticTokensDeltaResult(const QJsonValue &value) +{ + if (value.isObject()) { + QJsonObject object = value.toObject(); + if (object.contains(editsKey)) + emplace<SemanticTokensDelta>(object); + else + emplace<SemanticTokens>(object); + } else { + emplace<std::nullptr_t>(nullptr); + } +} + +SemanticTokensRefreshRequest::SemanticTokensRefreshRequest() + : Request(methodName, nullptr) +{} + +} // namespace lsp diff --git a/src/shared/lsp/semantictokens.h b/src/shared/lsp/semantictokens.h new file mode 100644 index 000000000..2dce74aa4 --- /dev/null +++ b/src/shared/lsp/semantictokens.h @@ -0,0 +1,230 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonkeys.h" +#include "jsonobject.h" +#include "jsonrpcmessages.h" +#include "languageserverprotocol_global.h" +#include "lsptypes.h" + +namespace lsp { + +struct LANGUAGESERVERPROTOCOL_EXPORT SemanticToken +{ + int deltaLine = 0; + int deltaStart = 0; + int length = 0; + int tokenIndex = 0; + int tokenType = 0; + int rawTokenModifiers = 0; + int tokenModifiers = 0; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensLegend : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The token types a server uses. + QList<QString> tokenTypes() const { return array<QString>(tokenTypesKey); } + void setTokenTypes(const QList<QString> &tokenTypes) { insertArray(tokenTypesKey, tokenTypes); } + + // The token modifiers a server uses. + QList<QString> tokenModifiers() const { return array<QString>(tokenModifiersKey); } + void setTokenModifiers(const QList<QString> &value) { insertArray(tokenModifiersKey, value); } + + bool isValid() const override; +}; + +enum SemanticTokenTypes { + namespaceToken, + typeToken, + classToken, + enumToken, + interfaceToken, + structToken, + typeParameterToken, + parameterToken, + variableToken, + propertyToken, + enumMemberToken, + eventToken, + functionToken, + methodToken, + macroToken, + keywordToken, + modifierToken, + commentToken, + stringToken, + numberToken, + regexpToken, + operatorToken +}; + +enum SemanticTokenModifiers { + declarationModifier = 0x1, + definitionModifier = 0x2, + readonlyModifier = 0x4, + staticModifier = 0x8, + deprecatedModifier = 0x10, + abstractModifier = 0x20, + asyncModifier = 0x40, + modificationModifier = 0x80, + documentationModifier = 0x100, + defaultLibraryModifier = 0x200 +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + bool isValid() const override { return contains(textDocumentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensDeltaParams : public SemanticTokensParams +{ +public: + using SemanticTokensParams::SemanticTokensParams; + + QString previousResultId() const { return typedValue<QString>(previousResultIdKey); } + void setPreviousResultId(const QString &previousResultId) + { + insert(previousResultIdKey, previousResultId); + } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRangeParams : public SemanticTokensParams +{ +public: + using SemanticTokensParams::SemanticTokensParams; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + bool isValid() const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokens : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * An optional result id. If provided and clients support delta updating + * the client will include the result id in the next semantic token request. + * A server can then instead of computing all semantic tokens again simply + * send a delta. + */ + std::optional<QString> resultId() const { return optionalValue<QString>(resultIdKey); } + void setResultId(const QString &resultId) { insert(resultIdKey, resultId); } + void clearResultId() { remove(resultIdKey); } + + /// The actual tokens. + QList<int> data() const { return array<int>(dataKey); } + void setData(const QList<int> &value) { insertArray(dataKey, value); } + + bool isValid() const override { return contains(dataKey); } + + QList<SemanticToken> toTokens(const QList<int> &tokenTypes, + const QList<int> &tokenModifiers) const; + static QMap<QString, int> defaultTokenTypesMap(); + static QMap<QString, int> defaultTokenModifiersMap(); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensResult + : public std::variant<SemanticTokens, std::nullptr_t> +{ +public: + using variant::variant; + explicit SemanticTokensResult(const QJsonValue &value); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensFullRequest + : public Request<SemanticTokensResult, std::nullptr_t, SemanticTokensParams> +{ +public: + explicit SemanticTokensFullRequest(const SemanticTokensParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/semanticTokens/full"; +}; + +class SemanticTokensEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int start() const { return typedValue<int>(startKey); } + void setStart(int start) { insert(startKey, start); } + + int deleteCount() const { return typedValue<int>(deleteCountKey); } + void setDeleteCount(int deleteCount) { insert(deleteCountKey, deleteCount); } + + std::optional<QList<int>> data() const { return optionalArray<int>(dataKey); } + void setData(const QList<int> &value) { insertArray(dataKey, value); } + void clearData() { remove(dataKey); } + + int dataSize() const { return contains(dataKey) ? value(dataKey).toArray().size() : 0; } + + bool isValid() const override { return contains(dataKey) && contains(deleteCountKey); } +}; + +class SemanticTokensDelta : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString resultId() const { return typedValue<QString>(resultIdKey); } + void setResultId(const QString &resultId) { insert(resultIdKey, resultId); } + + QList<SemanticTokensEdit> edits() const { return array<SemanticTokensEdit>(editsKey); } + void setEdits(const QList<SemanticTokensEdit> &edits) { insertArray(editsKey, edits); } + + bool isValid() const override { return contains(resultIdKey) && contains(editsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensDeltaResult + : public std::variant<SemanticTokens, SemanticTokensDelta, std::nullptr_t> +{ +public: + using variant::variant; + explicit SemanticTokensDeltaResult(const QJsonValue &value); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensFullDeltaRequest + : public Request<SemanticTokensDeltaResult, std::nullptr_t, SemanticTokensDeltaParams> +{ +public: + explicit SemanticTokensFullDeltaRequest(const SemanticTokensDeltaParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/semanticTokens/full/delta"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRangeRequest + : public Request<SemanticTokensResult, std::nullptr_t, SemanticTokensRangeParams> +{ +public: + explicit SemanticTokensRangeRequest(const SemanticTokensRangeParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/semanticTokens/range"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRefreshRequest + : public Request<std::nullptr_t, std::nullptr_t, std::nullptr_t> +{ +public: + explicit SemanticTokensRefreshRequest(); + using Request::Request; + constexpr static const char methodName[] = "workspace/semanticTokens/refresh"; +}; + +} // namespace lsp diff --git a/src/shared/lsp/servercapabilities.cpp b/src/shared/lsp/servercapabilities.cpp new file mode 100644 index 000000000..0923f76f3 --- /dev/null +++ b/src/shared/lsp/servercapabilities.cpp @@ -0,0 +1,386 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "servercapabilities.h" + +namespace lsp { + +std::optional<ServerCapabilities::TextDocumentSync> ServerCapabilities::textDocumentSync() const +{ + const QJsonValue &sync = value(textDocumentSyncKey); + if (sync.isUndefined()) + return std::nullopt; + return std::make_optional(sync.isDouble() + ? TextDocumentSync(sync.toInt()) + : TextDocumentSync(TextDocumentSyncOptions(sync.toObject()))); +} + +void ServerCapabilities::setTextDocumentSync(const ServerCapabilities::TextDocumentSync &textDocumentSync) +{ + insertVariant<TextDocumentSyncOptions, int>(textDocumentSyncKey, textDocumentSync); +} + +TextDocumentSyncKind ServerCapabilities::textDocumentSyncKindHelper() +{ + if (std::optional<TextDocumentSync> sync = textDocumentSync()) { + if (auto kind = std::get_if<int>(&*sync)) + return static_cast<TextDocumentSyncKind>(*kind); + if (auto options = std::get_if<TextDocumentSyncOptions>(&*sync)) { + if (const std::optional<int> &change = options->change()) + return static_cast<TextDocumentSyncKind>(*change); + } + } + return TextDocumentSyncKind::None; +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> ServerCapabilities::hoverProvider() + const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(hoverProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setHoverProvider( + const std::variant<bool, WorkDoneProgressOptions> &hoverProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(hoverProviderKey, hoverProvider); +} + +std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>> +ServerCapabilities::definitionProvider() const +{ + using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>; + const QJsonValue &provider = value(definitionProviderKey); + if (provider.isUndefined() || !(provider.isBool() || provider.isObject())) + return std::nullopt; + return std::make_optional(provider.isBool() + ? RetType(provider.toBool()) + : RetType(RegistrationOptions(provider.toObject()))); +} + +void ServerCapabilities::setDefinitionProvider( + const std::variant<bool, RegistrationOptions> &definitionProvider) +{ + insertVariant<bool, RegistrationOptions>(definitionProviderKey, definitionProvider); +} + +std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>> +ServerCapabilities::typeDefinitionProvider() const +{ + using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>; + const QJsonValue &provider = value(typeDefinitionProviderKey); + if (provider.isUndefined() || !(provider.isBool() || provider.isObject())) + return std::nullopt; + return std::make_optional(provider.isBool() + ? RetType(provider.toBool()) + : RetType(RegistrationOptions(provider.toObject()))); +} + +void ServerCapabilities::setTypeDefinitionProvider( + const std::variant<bool, ServerCapabilities::RegistrationOptions> &typeDefinitionProvider) +{ + insertVariant<bool, ServerCapabilities::RegistrationOptions>(typeDefinitionProviderKey, + typeDefinitionProvider); +} + +std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>> +ServerCapabilities::implementationProvider() const +{ + using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>; + const QJsonValue &provider = value(implementationProviderKey); + if (provider.isUndefined() || !(provider.isBool() || provider.isObject())) + return std::nullopt; + return std::make_optional(provider.isBool() + ? RetType(provider.toBool()) + : RetType(RegistrationOptions(provider.toObject()))); +} + +void ServerCapabilities::setImplementationProvider( + const std::variant<bool, ServerCapabilities::RegistrationOptions> &implementationProvider) +{ + insertVariant<bool, RegistrationOptions>(implementationProviderKey, implementationProvider); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::referencesProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(referencesProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setReferencesProvider( + const std::variant<bool, WorkDoneProgressOptions> &referencesProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(referencesProviderKey, + referencesProvider); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::documentHighlightProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(documentHighlightProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setDocumentHighlightProvider( + const std::variant<bool, WorkDoneProgressOptions> &documentHighlightProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(documentHighlightProviderKey, + documentHighlightProvider); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::documentSymbolProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(documentSymbolProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setDocumentSymbolProvider( + std::variant<bool, WorkDoneProgressOptions> documentSymbolProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(documentSymbolProviderKey, + documentSymbolProvider); +} + +std::optional<SemanticTokensOptions> ServerCapabilities::semanticTokensProvider() const +{ + return optionalValue<SemanticTokensOptions>(semanticTokensProviderKey); +} + +void ServerCapabilities::setSemanticTokensProvider( + const SemanticTokensOptions &semanticTokensProvider) +{ + insert(semanticTokensProviderKey, semanticTokensProvider); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions> > +ServerCapabilities::callHierarchyProvider() const +{ + const QJsonValue &provider = value(callHierarchyProviderKey); + if (provider.isBool()) + return provider.toBool(); + else if (provider.isObject()) + return WorkDoneProgressOptions(provider.toObject()); + return std::nullopt; +} + +void ServerCapabilities::setCallHierarchyProvider( + const std::variant<bool, WorkDoneProgressOptions> &callHierarchyProvider) +{ + QJsonValue val; + if (std::holds_alternative<bool>(callHierarchyProvider)) + val = std::get<bool>(callHierarchyProvider); + else if (std::holds_alternative<WorkDoneProgressOptions>(callHierarchyProvider)) + val = QJsonObject(std::get<WorkDoneProgressOptions>(callHierarchyProvider)); + insert(callHierarchyProviderKey, val); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::workspaceSymbolProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(workspaceSymbolProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setWorkspaceSymbolProvider( + std::variant<bool, WorkDoneProgressOptions> workspaceSymbolProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(workspaceSymbolProviderKey, + workspaceSymbolProvider); +} + +std::optional<std::variant<bool, CodeActionOptions>> ServerCapabilities::codeActionProvider() const +{ + const QJsonValue &provider = value(codeActionProviderKey); + if (provider.isBool()) + return std::make_optional(std::variant<bool, CodeActionOptions>(provider.toBool())); + if (provider.isObject()) { + CodeActionOptions options(provider); + if (options.isValid()) + return std::make_optional(std::variant<bool, CodeActionOptions>(options)); + } + return std::nullopt; +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::documentFormattingProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(documentFormattingProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setDocumentFormattingProvider( + const std::variant<bool, WorkDoneProgressOptions> &documentFormattingProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(documentFormattingProviderKey, + documentFormattingProvider); +} + +std::optional<std::variant<bool, WorkDoneProgressOptions>> +ServerCapabilities::documentRangeFormattingProvider() const +{ + using RetType = std::variant<bool, WorkDoneProgressOptions>; + const QJsonValue &provider = value(documentRangeFormattingProviderKey); + if (provider.isBool()) + return std::make_optional(RetType(provider.toBool())); + if (provider.isObject()) + return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject()))); + return std::nullopt; +} + +void ServerCapabilities::setDocumentRangeFormattingProvider( + std::variant<bool, WorkDoneProgressOptions> documentRangeFormattingProvider) +{ + insertVariant<bool, WorkDoneProgressOptions>(documentRangeFormattingProviderKey, + documentRangeFormattingProvider); +} + +std::optional<std::variant<ServerCapabilities::RenameOptions, bool>> ServerCapabilities::renameProvider() const +{ + using RetType = std::variant<ServerCapabilities::RenameOptions, bool>; + const QJsonValue &localValue = value(renameProviderKey); + if (localValue.isBool()) + return RetType(localValue.toBool()); + if (localValue.isObject()) + return RetType(RenameOptions(localValue.toObject())); + return std::nullopt; +} + +void ServerCapabilities::setRenameProvider(std::variant<ServerCapabilities::RenameOptions, bool> renameProvider) +{ + insertVariant<RenameOptions, bool>(renameProviderKey, renameProvider); +} + +std::optional<std::variant<bool, JsonObject>> ServerCapabilities::colorProvider() const +{ + using RetType = std::variant<bool, JsonObject>; + const QJsonValue &localValue = value(colorProviderKey); + if (localValue.isBool()) + return RetType(localValue.toBool()); + if (localValue.isObject()) + return RetType(JsonObject(localValue.toObject())); + return std::nullopt; +} + +void ServerCapabilities::setColorProvider(std::variant<bool, JsonObject> colorProvider) +{ + insertVariant<bool, JsonObject>(renameProviderKey, colorProvider); +} + +std::optional<std::variant<QString, bool> > +ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::changeNotifications() const +{ + using RetType = std::variant<QString, bool>; + const QJsonValue &change = value(changeNotificationsKey); + if (change.isUndefined()) + return std::nullopt; + return std::make_optional(change.isBool() ? RetType(change.toBool()) + : RetType(change.toString())); +} + +void ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::setChangeNotifications( + std::variant<QString, bool> changeNotifications) +{ + insertVariant<QString, bool>(changeNotificationsKey, changeNotifications); +} + +bool TextDocumentRegistrationOptions::filterApplies(const Utils::FilePath &fileName) const +{ + Q_UNUSED(fileName) + return true; +} + +bool ServerCapabilities::ExecuteCommandOptions::isValid() const +{ + return WorkDoneProgressOptions::isValid() && contains(commandsKey); +} + +bool CodeActionOptions::isValid() const +{ + return WorkDoneProgressOptions::isValid() && contains(codeActionKindsKey); +} + +std::optional<std::variant<bool, QJsonObject>> SemanticTokensOptions::range() const +{ + using RetType = std::variant<bool, QJsonObject>; + const QJsonValue &rangeOptions = value(rangeKey); + if (rangeOptions.isBool()) + return RetType(rangeOptions.toBool()); + if (rangeOptions.isObject()) + return RetType(rangeOptions.toObject()); + return std::nullopt; +} + +void SemanticTokensOptions::setRange(const std::variant<bool, QJsonObject> &range) +{ + insertVariant<bool, QJsonObject>(rangeKey, range); +} + +std::optional<std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions>> +SemanticTokensOptions::full() const +{ + using RetType = std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions>; + const QJsonValue &fullOptions = value(fullKey); + if (fullOptions.isBool()) + return RetType(fullOptions.toBool()); + if (fullOptions.isObject()) + return RetType(FullSemanticTokenOptions(fullOptions.toObject())); + return std::nullopt; +} + +void SemanticTokensOptions::setFull( + const std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions> &full) +{ + insertVariant<bool, FullSemanticTokenOptions>(fullKey, full); +} + +SemanticRequestTypes SemanticTokensOptions::supportedRequests() const +{ + SemanticRequestTypes result; + QJsonValue rangeValue = value(rangeKey); + if (rangeValue.isObject() || rangeValue.toBool()) + result |= SemanticRequestType::Range; + QJsonValue fullValue = value(fullKey); + if (fullValue.isObject()) { + SemanticTokensOptions::FullSemanticTokenOptions options(fullValue.toObject()); + if (options.delta().value_or(false)) + result |= SemanticRequestType::FullDelta; + result |= SemanticRequestType::Full; + } else if (fullValue.toBool()) { + result |= SemanticRequestType::Full; + } + return result; +} + +} // namespace lsp diff --git a/src/shared/lsp/servercapabilities.h b/src/shared/lsp/servercapabilities.h new file mode 100644 index 000000000..03c0371e8 --- /dev/null +++ b/src/shared/lsp/servercapabilities.h @@ -0,0 +1,435 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "lsptypes.h" +#include "semantictokens.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<bool> workDoneProgress() const { return optionalValue<bool>(workDoneProgressKey); } + void setWorkDoneProgress(bool workDoneProgress) { insert(workDoneProgressKey, workDoneProgress); } + void clearWorkDoneProgress() { remove(workDoneProgressKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ResolveProviderOption : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<bool> resolveProvider() const { return optionalValue<bool>(resolveProviderKey); } + void setResolveProvider(bool resolveProvider) { insert(resolveProviderKey, resolveProvider); } + void clearResolveProvider() { remove(resolveProviderKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentRegistrationOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + LanguageClientArray<DocumentFilter> documentSelector() const + { return clientArray<DocumentFilter>(documentSelectorKey); } + void setDocumentSelector(const LanguageClientArray<DocumentFilter> &documentSelector) + { insert(documentSelectorKey, documentSelector.toJson()); } + + bool filterApplies(const Utils::FilePath &fileName) const; + + bool isValid() const override { return contains(documentSelectorKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SaveOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The client is supposed to include the content on save. + std::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); } + void setIncludeText(bool includeText) { insert(includeTextKey, includeText); } + void clearIncludeText() { remove(includeTextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSyncOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Open and close notifications are sent to the server. + std::optional<bool> openClose() const { return optionalValue<bool>(openCloseKey); } + void setOpenClose(bool openClose) { insert(openCloseKey, openClose); } + void clearOpenClose() { remove(openCloseKey); } + + // Change notifications are sent to the server. See TextDocumentSyncKind.None, + // TextDocumentSyncKind.Full and TextDocumentSyncKind.Incremental. + std::optional<int> change() const { return optionalValue<int>(changeKey); } + void setChange(int change) { insert(changeKey, change); } + void clearChange() { remove(changeKey); } + + // Will save notifications are sent to the server. + std::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); } + void setWillSave(bool willSave) { insert(willSaveKey, willSave); } + void clearWillSave() { remove(willSaveKey); } + + // Will save wait until requests are sent to the server. + std::optional<bool> willSaveWaitUntil() const + { return optionalValue<bool>(willSaveWaitUntilKey); } + void setWillSaveWaitUntil(bool willSaveWaitUntil) + { insert(willSaveWaitUntilKey, willSaveWaitUntil); } + void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); } + + // Save notifications are sent to the server. + std::optional<SaveOptions> save() const { return optionalValue<SaveOptions>(saveKey); } + void setSave(const SaveOptions &save) { insert(saveKey, save); } + void clearSave() { remove(saveKey); } +}; + +enum class TextDocumentSyncKind +{ + // Documents should not be synced at all. + None = 0, + // Documents are synced by always sending the full content of the document. + Full = 1, + // Documents are synced by sending the full content on open. + // After that only incremental updates to the document are send. + Incremental = 2 +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionOptions : public WorkDoneProgressOptions +{ +public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + QList<QString> codeActionKinds() const { return array<QString>(codeActionKindsKey); } + void setCodeActionKinds(const QList<QString> &codeActionKinds) + { insertArray(codeActionKindsKey, codeActionKinds); } + + bool isValid() const override; +}; + +enum class SemanticRequestType { + None = 0x0, + Full = 0x1, + FullDelta = 0x2, + Range = 0x4 +}; +Q_DECLARE_FLAGS(SemanticRequestTypes, SemanticRequestType) + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensOptions : public WorkDoneProgressOptions +{ +public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + /// The legend used by the server + SemanticTokensLegend legend() const { return typedValue<SemanticTokensLegend>(legendKey); } + void setLegend(const SemanticTokensLegend &legend) { insert(legendKey, legend); } + + /// Server supports providing semantic tokens for a specific range of a document. + std::optional<std::variant<bool, QJsonObject>> range() const; + void setRange(const std::variant<bool, QJsonObject> &range); + void clearRange() { remove(rangeKey); } + + class FullSemanticTokenOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + /// The server supports deltas for full documents. + std::optional<bool> delta() const { return optionalValue<bool>(deltaKey); } + void setDelta(bool delta) { insert(deltaKey, delta); } + void clearDelta() { remove(deltaKey); } + }; + + /// Server supports providing semantic tokens for a full document. + std::optional<std::variant<bool, FullSemanticTokenOptions>> full() const; + void setFull(const std::variant<bool, FullSemanticTokenOptions> &full); + void clearFull() { remove(fullKey); } + + bool isValid() const override { return contains(legendKey); } + + SemanticRequestTypes supportedRequests() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ServerCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Defines how the host (editor) should sync document changes to the language server. + + class LANGUAGESERVERPROTOCOL_EXPORT CompletionOptions : public WorkDoneProgressOptions + { + public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + // The characters that trigger completion automatically. + std::optional<QList<QString>> triggerCharacters() const + { return optionalArray<QString>(triggerCharactersKey); } + void setTriggerCharacters(const QList<QString> &triggerCharacters) + { insertArray(triggerCharactersKey, triggerCharacters); } + void clearTriggerCharacters() { remove(triggerCharactersKey); } + + std::optional<bool> resolveProvider() const { return optionalValue<bool>(resolveProviderKey); } + void setResolveProvider(bool resolveProvider) { insert(resolveProviderKey, resolveProvider); } + void clearResolveProvider() { remove(resolveProviderKey); } + }; + + class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpOptions : public WorkDoneProgressOptions + { + public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + // The characters that trigger signature help automatically. + std::optional<QList<QString>> triggerCharacters() const + { return optionalArray<QString>(triggerCharactersKey); } + void setTriggerCharacters(const QList<QString> &triggerCharacters) + { insertArray(triggerCharactersKey, triggerCharacters); } + void clearTriggerCharacters() { remove(triggerCharactersKey); } + }; + + using CodeLensOptions = ResolveProviderOption; + + class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + // A character on which formatting should be triggered, like `}`. + QString firstTriggerCharacter() const { return typedValue<QString>(firstTriggerCharacterKey); } + void setFirstTriggerCharacter(QString firstTriggerCharacter) + { insert(firstTriggerCharacterKey, firstTriggerCharacter); } + + // More trigger characters. + std::optional<QList<QString>> moreTriggerCharacter() const + { return optionalArray<QString>(moreTriggerCharacterKey); } + void setMoreTriggerCharacter(const QList<QString> &moreTriggerCharacter) + { insertArray(moreTriggerCharacterKey, moreTriggerCharacter); } + void clearMoreTriggerCharacter() { remove(moreTriggerCharacterKey); } + + bool isValid() const override { return contains(firstTriggerCharacterKey); } + }; + + using DocumentLinkOptions = ResolveProviderOption; + + class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandOptions : public WorkDoneProgressOptions + { + public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + QList<QString> commands() const { return array<QString>(commandsKey); } + void setCommands(const QList<QString> &commands) { insertArray(commandsKey, commands); } + + bool isValid() const override; + }; + + using ColorProviderOptions = JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT StaticRegistrationOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The id used to register the request. The id can be used to deregister + // the request again. See also Registration#id. + std::optional<QString> id() const { return optionalValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + void clearId() { remove(idKey); } + }; + + // Defines how text documents are synced. Is either a detailed structure defining each + // notification or for backwards compatibility the TextDocumentSyncKind number. + using TextDocumentSync = std::variant<TextDocumentSyncOptions, int>; + std::optional<TextDocumentSync> textDocumentSync() const; + void setTextDocumentSync(const TextDocumentSync &textDocumentSync); + void clearTextDocumentSync() { remove(textDocumentSyncKey); } + + TextDocumentSyncKind textDocumentSyncKindHelper(); + + // The server provides hover support. + std::optional<std::variant<bool, WorkDoneProgressOptions>> hoverProvider() const; + void setHoverProvider(const std::variant<bool, WorkDoneProgressOptions> &hoverProvider); + void clearHoverProvider() { remove(hoverProviderKey); } + + // The server provides completion support. + std::optional<CompletionOptions> completionProvider() const + { return optionalValue<CompletionOptions>(completionProviderKey); } + void setCompletionProvider(const CompletionOptions &completionProvider) + { insert(completionProviderKey, completionProvider); } + void clearCompletionProvider() { remove(completionProviderKey); } + + // The server provides signature help support. + std::optional<SignatureHelpOptions> signatureHelpProvider() const + { return optionalValue<SignatureHelpOptions>(signatureHelpProviderKey); } + void setSignatureHelpProvider(const SignatureHelpOptions &signatureHelpProvider) + { insert(signatureHelpProviderKey, signatureHelpProvider); } + void clearSignatureHelpProvider() { remove(signatureHelpProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT RegistrationOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + LanguageClientArray<DocumentFilter> documentSelector() const + { return clientArray<DocumentFilter>(documentSelectorKey); } + void setDocumentSelector(const LanguageClientArray<DocumentFilter> &documentSelector) + { insert(documentSelectorKey, documentSelector.toJson()); } + + bool filterApplies(const Utils::FilePath &fileName) const; + + // The id used to register the request. The id can be used to deregister + // the request again. See also Registration#id. + std::optional<QString> id() const { return optionalValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + void clearId() { remove(idKey); } + + bool isValid() const override { return contains(documentSelectorKey); } + }; + + // The server provides goto definition support. + std::optional<std::variant<bool, RegistrationOptions>> definitionProvider() const; + void setDefinitionProvider(const std::variant<bool, RegistrationOptions> &typeDefinitionProvider); + void clearDefinitionProvider() { remove(typeDefinitionProviderKey); } + + // The server provides Goto Type Definition support. + std::optional<std::variant<bool, RegistrationOptions>> typeDefinitionProvider() const; + void setTypeDefinitionProvider(const std::variant<bool, RegistrationOptions> &typeDefinitionProvider); + void clearTypeDefinitionProvider() { remove(typeDefinitionProviderKey); } + + // The server provides Goto Implementation support. + std::optional<std::variant<bool, RegistrationOptions>> implementationProvider() const; + void setImplementationProvider(const std::variant<bool, RegistrationOptions> &implementationProvider); + void clearImplementationProvider() { remove(implementationProviderKey); } + + // The server provides find references support. + std::optional<std::variant<bool, WorkDoneProgressOptions>> referencesProvider() const; + void setReferencesProvider(const std::variant<bool, WorkDoneProgressOptions> &referencesProvider); + void clearReferencesProvider() { remove(referencesProviderKey); } + + // The server provides document highlight support. + std::optional<std::variant<bool, WorkDoneProgressOptions>> documentHighlightProvider() const; + void setDocumentHighlightProvider( + const std::variant<bool, WorkDoneProgressOptions> &documentHighlightProvider); + void clearDocumentHighlightProvider() { remove(documentHighlightProviderKey); } + + // The server provides document symbol support. + std::optional<std::variant<bool, WorkDoneProgressOptions>> documentSymbolProvider() const; + void setDocumentSymbolProvider(std::variant<bool, WorkDoneProgressOptions> documentSymbolProvider); + void clearDocumentSymbolProvider() { remove(documentSymbolProviderKey); } + + std::optional<SemanticTokensOptions> semanticTokensProvider() const; + void setSemanticTokensProvider(const SemanticTokensOptions &semanticTokensProvider); + void clearSemanticTokensProvider() { remove(semanticTokensProviderKey); } + + std::optional<std::variant<bool, WorkDoneProgressOptions>> callHierarchyProvider() const; + void setCallHierarchyProvider(const std::variant<bool, WorkDoneProgressOptions> &callHierarchyProvider); + void clearCallHierarchyProvider() { remove(callHierarchyProviderKey); } + + // The server provides workspace symbol support. + std::optional<std::variant<bool, WorkDoneProgressOptions>> workspaceSymbolProvider() const; + void setWorkspaceSymbolProvider(std::variant<bool, WorkDoneProgressOptions> workspaceSymbolProvider); + void clearWorkspaceSymbolProvider() { remove(workspaceSymbolProviderKey); } + + // The server provides code actions. + std::optional<std::variant<bool, CodeActionOptions>> codeActionProvider() const; + void setCodeActionProvider(bool codeActionProvider) + { insert(codeActionProviderKey, codeActionProvider); } + void setCodeActionProvider(CodeActionOptions options) + { insert(codeActionProviderKey, options); } + void clearCodeActionProvider() { remove(codeActionProviderKey); } + + // The server provides code lens. + std::optional<CodeLensOptions> codeLensProvider() const + { return optionalValue<CodeLensOptions>(codeLensProviderKey); } + void setCodeLensProvider(CodeLensOptions codeLensProvider) + { insert(codeLensProviderKey, codeLensProvider); } + void clearCodeLensProvider() { remove(codeLensProviderKey); } + + // The server provides document formatting. + std::optional<std::variant<bool, WorkDoneProgressOptions>> documentFormattingProvider() const; + void setDocumentFormattingProvider( + const std::variant<bool, WorkDoneProgressOptions> &documentFormattingProvider); + void clearDocumentFormattingProvider() { remove(documentFormattingProviderKey); } + + // The server provides document formatting on typing. + std::optional<std::variant<bool, WorkDoneProgressOptions>> documentRangeFormattingProvider() const; + void setDocumentRangeFormattingProvider(std::variant<bool, WorkDoneProgressOptions> documentRangeFormattingProvider); + void clearDocumentRangeFormattingProvider() { remove(documentRangeFormattingProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT RenameOptions : public WorkDoneProgressOptions + { + public: + using WorkDoneProgressOptions::WorkDoneProgressOptions; + + // Renames should be checked and tested before being executed. + std::optional<bool> prepareProvider() const { return optionalValue<bool>(prepareProviderKey); } + void setPrepareProvider(bool prepareProvider) { insert(prepareProviderKey, prepareProvider); } + void clearPrepareProvider() { remove(prepareProviderKey); } + }; + + // The server provides rename support. + std::optional<std::variant<RenameOptions, bool>> renameProvider() const; + void setRenameProvider(std::variant<RenameOptions,bool> renameProvider); + void clearRenameProvider() { remove(renameProviderKey); } + + // The server provides document link support. + std::optional<DocumentLinkOptions> documentLinkProvider() const + { return optionalValue<DocumentLinkOptions>(documentLinkProviderKey); } + void setDocumentLinkProvider(const DocumentLinkOptions &documentLinkProvider) + { insert(documentLinkProviderKey, documentLinkProvider); } + void clearDocumentLinkProvider() { remove(documentLinkProviderKey); } + + // The server provides color provider support. + std::optional<std::variant<bool, JsonObject>> colorProvider() const; + void setColorProvider(std::variant<bool, JsonObject> colorProvider); + void clearColorProvider() { remove(colorProviderKey); } + + // The server provides execute command support. + std::optional<ExecuteCommandOptions> executeCommandProvider() const + { return optionalValue<ExecuteCommandOptions>(executeCommandProviderKey); } + void setExecuteCommandProvider(ExecuteCommandOptions executeCommandProvider) + { insert(executeCommandProviderKey, executeCommandProvider); } + void clearExecuteCommandProvider() { remove(executeCommandProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceServerCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The server has support for workspace folders + std::optional<bool> supported() const { return optionalValue<bool>(supportedKey); } + void setSupported(bool supported) { insert(supportedKey, supported); } + void clearSupported() { remove(supportedKey); } + + std::optional<std::variant<QString, bool>> changeNotifications() const; + void setChangeNotifications(std::variant<QString, bool> changeNotifications); + void clearChangeNotifications() { remove(changeNotificationsKey); } + }; + + std::optional<WorkspaceFoldersCapabilities> workspaceFolders() const + { return optionalValue<WorkspaceFoldersCapabilities>(workspaceFoldersKey); } + void setWorkspaceFolders(const WorkspaceFoldersCapabilities &workspaceFolders) + { insert(workspaceFoldersKey, workspaceFolders); } + void clearWorkspaceFolders() { remove(workspaceFoldersKey); } + }; + + std::optional<WorkspaceServerCapabilities> workspace() const + { return optionalValue<WorkspaceServerCapabilities>(workspaceKey); } + void setWorkspace(const WorkspaceServerCapabilities &workspace) + { insert(workspaceKey, workspace); } + void clearWorkspace() { remove(workspaceKey); } + + std::optional<JsonObject> experimental() const { return optionalValue<JsonObject>(experimentalKey); } + void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); } + void clearExperimental() { remove(experimentalKey); } +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/shutdownmessages.cpp b/src/shared/lsp/shutdownmessages.cpp new file mode 100644 index 000000000..21c41c1d3 --- /dev/null +++ b/src/shared/lsp/shutdownmessages.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "shutdownmessages.h" + +namespace lsp { + +constexpr const char ShutdownRequest::methodName[]; +constexpr const char ExitNotification::methodName[]; +ShutdownRequest::ShutdownRequest() : Request(methodName, nullptr) { } +ExitNotification::ExitNotification() : Notification(methodName) { } + +} // namespace lsp diff --git a/src/shared/lsp/shutdownmessages.h b/src/shared/lsp/shutdownmessages.h new file mode 100644 index 000000000..2e8da4c66 --- /dev/null +++ b/src/shared/lsp/shutdownmessages.h @@ -0,0 +1,29 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT ShutdownRequest : public Request< + std::nullptr_t, std::nullptr_t, std::nullptr_t> +{ +public: + ShutdownRequest(); + using Request::Request; + constexpr static const char methodName[] = "shutdown"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExitNotification : public Notification<std::nullptr_t> +{ +public: + ExitNotification(); + using Notification::Notification; + constexpr static const char methodName[] = "exit"; + + bool parametersAreValid(QString * /*errorMessage*/) const final { return true; } +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/textsynchronization.cpp b/src/shared/lsp/textsynchronization.cpp new file mode 100644 index 000000000..ba9ee2f32 --- /dev/null +++ b/src/shared/lsp/textsynchronization.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "textsynchronization.h" + +namespace lsp { + +constexpr const char DidOpenTextDocumentNotification::methodName[]; +constexpr const char DidChangeTextDocumentNotification::methodName[]; +constexpr const char WillSaveTextDocumentNotification::methodName[]; +constexpr const char WillSaveWaitUntilTextDocumentRequest::methodName[]; +constexpr const char DidSaveTextDocumentNotification::methodName[]; +constexpr const char DidCloseTextDocumentNotification::methodName[]; + +DidOpenTextDocumentNotification::DidOpenTextDocumentNotification( + const DidOpenTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeTextDocumentNotification::DidChangeTextDocumentNotification( + const DidChangeTextDocumentParams ¶ms) + : DidChangeTextDocumentNotification(methodName, params) +{ } + +WillSaveTextDocumentNotification::WillSaveTextDocumentNotification( + const WillSaveTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +WillSaveWaitUntilTextDocumentRequest::WillSaveWaitUntilTextDocumentRequest(const WillSaveTextDocumentParams ¶ms) + : Request(methodName, params) +{ } + +DidSaveTextDocumentNotification::DidSaveTextDocumentNotification( + const DidSaveTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidCloseTextDocumentNotification::DidCloseTextDocumentNotification( + const DidCloseTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeTextDocumentParams::DidChangeTextDocumentParams() + : DidChangeTextDocumentParams(VersionedTextDocumentIdentifier()) +{ } + +DidChangeTextDocumentParams::DidChangeTextDocumentParams( + const VersionedTextDocumentIdentifier &docId, const QString &text) +{ + setTextDocument(docId); + setContentChanges({TextDocumentContentChangeEvent(text)}); +} + +DidOpenTextDocumentParams::DidOpenTextDocumentParams(const TextDocumentItem &document) +{ + setTextDocument(document); +} + +DidCloseTextDocumentParams::DidCloseTextDocumentParams(const TextDocumentIdentifier &document) +{ + setTextDocument(document); +} + +DidChangeTextDocumentParams::TextDocumentContentChangeEvent::TextDocumentContentChangeEvent( + const QString &text) +{ + setText(text); +} + +DidSaveTextDocumentParams::DidSaveTextDocumentParams(const TextDocumentIdentifier &document) +{ + setTextDocument(document); +} + +WillSaveTextDocumentParams::WillSaveTextDocumentParams( + const TextDocumentIdentifier &document, + const WillSaveTextDocumentParams::TextDocumentSaveReason &reason) +{ + setTextDocument(document); + setReason(reason); +} + +} // namespace lsp diff --git a/src/shared/lsp/textsynchronization.h b/src/shared/lsp/textsynchronization.h new file mode 100644 index 000000000..9ec3d4164 --- /dev/null +++ b/src/shared/lsp/textsynchronization.h @@ -0,0 +1,215 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" +#include "servercapabilities.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentParams : public JsonObject +{ +public: + DidOpenTextDocumentParams() = default; + explicit DidOpenTextDocumentParams(const TextDocumentItem &document); + using JsonObject::JsonObject; + + TextDocumentItem textDocument() const { return typedValue<TextDocumentItem>(textDocumentKey); } + void setTextDocument(TextDocumentItem textDocument) { insert(textDocumentKey, textDocument); } + + bool isValid() const override { return contains(textDocumentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentNotification : public Notification< + DidOpenTextDocumentParams> +{ +public: + explicit DidOpenTextDocumentNotification(const DidOpenTextDocumentParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didOpen"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentChangeRegistrationOptions : public JsonObject +{ +public: + TextDocumentChangeRegistrationOptions(); + explicit TextDocumentChangeRegistrationOptions(TextDocumentSyncKind kind); + using JsonObject::JsonObject; + + TextDocumentSyncKind syncKind() const + { return static_cast<TextDocumentSyncKind>(typedValue<int>(syncKindKey)); } + void setSyncKind(TextDocumentSyncKind syncKind) + { insert(syncKindKey, static_cast<int>(syncKind)); } + + bool isValid() const override { return contains(syncKindKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentParams : public JsonObject +{ +public: + DidChangeTextDocumentParams(); + explicit DidChangeTextDocumentParams(const VersionedTextDocumentIdentifier &docId, + const QString &text = QString()); + using JsonObject::JsonObject; + + VersionedTextDocumentIdentifier textDocument() const + { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const VersionedTextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentContentChangeEvent : public JsonObject + { + /* + * An event describing a change to a text document. If range and rangeLength are omitted + * the new text is considered to be the full content of the document. + */ + public: + TextDocumentContentChangeEvent() = default; + explicit TextDocumentContentChangeEvent(const QString &text); + using JsonObject::JsonObject; + + // The range of the document that changed. + std::optional<Range> range() const { return optionalValue<Range>(rangeKey); } + void setRange(Range range) { insert(rangeKey, range); } + void clearRange() { remove(rangeKey); } + + // The length of the range that got replaced. + std::optional<int> rangeLength() const { return optionalValue<int>(rangeLengthKey); } + void setRangeLength(int rangeLength) { insert(rangeLengthKey, rangeLength); } + void clearRangeLength() { remove(rangeLengthKey); } + + // The new text of the range/document. + QString text() const { return typedValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + + bool isValid() const override { return contains(textKey); } + }; + + QList<TextDocumentContentChangeEvent> contentChanges() const + { return array<TextDocumentContentChangeEvent>(contentChangesKey); } + void setContentChanges(const QList<TextDocumentContentChangeEvent> &contentChanges) + { insertArray(contentChangesKey, contentChanges); } + + bool isValid() const override + { return contains(textDocumentKey) && contains(contentChangesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentNotification : public Notification< + DidChangeTextDocumentParams> +{ +public: + explicit DidChangeTextDocumentNotification(const DidChangeTextDocumentParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didChange"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentParams : public JsonObject +{ +public: + enum class TextDocumentSaveReason { + Manual = 1, + AfterDelay = 2, + FocusOut = 3 + }; + + WillSaveTextDocumentParams() : WillSaveTextDocumentParams(TextDocumentIdentifier()) {} + explicit WillSaveTextDocumentParams( + const TextDocumentIdentifier &document, + const TextDocumentSaveReason &reason = TextDocumentSaveReason::Manual); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + TextDocumentSaveReason reason() const + { return static_cast<TextDocumentSaveReason>(typedValue<int>(reasonKey)); } + void setReason(TextDocumentSaveReason reason) { insert(reasonKey, static_cast<int>(reason)); } + + bool isValid() const override { return contains(textDocumentKey) && contains(reasonKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentNotification : public Notification< + WillSaveTextDocumentParams> +{ +public: + explicit WillSaveTextDocumentNotification(const WillSaveTextDocumentParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/willSave"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveWaitUntilTextDocumentRequest : public Request< + LanguageClientArray<TextEdit>, std::nullptr_t, WillSaveTextDocumentParams> +{ +public: + explicit WillSaveWaitUntilTextDocumentRequest(const WillSaveTextDocumentParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "textDocument/willSaveWaitUntil"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSaveRegistrationOptions + : public TextDocumentRegistrationOptions +{ +public: + using TextDocumentRegistrationOptions::TextDocumentRegistrationOptions; + + std::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); } + void setIncludeText(bool includeText) { insert(includeTextKey, includeText); } + void clearIncludeText() { remove(includeTextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentParams : public JsonObject +{ +public: + DidSaveTextDocumentParams() : DidSaveTextDocumentParams(TextDocumentIdentifier()) {} + explicit DidSaveTextDocumentParams(const TextDocumentIdentifier &document); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() + const { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(TextDocumentIdentifier textDocument) + { insert(textDocumentKey, textDocument); } + + std::optional<QString> text() const { return optionalValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + void clearText() { remove(textKey); } + + bool isValid() const override { return contains(textDocumentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentNotification : public Notification< + DidSaveTextDocumentParams> +{ +public: + explicit DidSaveTextDocumentNotification(const DidSaveTextDocumentParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didSave"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentParams : public JsonObject +{ +public: + DidCloseTextDocumentParams() = default; + explicit DidCloseTextDocumentParams(const TextDocumentIdentifier &document); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + bool isValid() const override { return contains(textDocumentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentNotification : public Notification< + DidCloseTextDocumentParams> +{ +public: + explicit DidCloseTextDocumentNotification(const DidCloseTextDocumentParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didClose"; +}; + +} // namespace LanguageClient diff --git a/src/shared/lsp/textutils.cpp b/src/shared/lsp/textutils.cpp new file mode 100644 index 000000000..1c0bff9d0 --- /dev/null +++ b/src/shared/lsp/textutils.cpp @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "textutils.h" + +#include <tools/qbsassert.h> + +#include <QRegularExpression> +#include <QtDebug> + +namespace lsp::Utils::Text { + +bool Position::operator==(const Position &other) const +{ + return line == other.line && column == other.column; +} + +/*! + Returns the text position of a \a fileName and sets the \a postfixPos if + it can find a positional postfix. + + The following patterns are supported: \c {filepath.txt:19}, + \c{filepath.txt:19:12}, \c {filepath.txt+19}, + \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. +*/ + +Position Position::fromFileName(QStringView fileName, int &postfixPos) +{ + static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); + // (10) MSVC-style + static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); + const QRegularExpressionMatch match = regexp.match(fileName); + Position pos; + if (match.hasMatch()) { + postfixPos = match.capturedStart(0); + if (match.lastCapturedIndex() > 0) { + pos.line = match.captured(1).toInt(); + if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number + pos.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based + } + } else { + const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); + postfixPos = vsMatch.capturedStart(0); + if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) + pos.line = vsMatch.captured(2).toInt(); + } + if (pos.line > 0 && pos.column < 0) + pos.column = 0; // if we got a valid line make sure to return a valid TextPosition + return pos; +} + +int Range::length(const QString &text) const +{ + if (end.line < begin.line) + return -1; + + if (begin.line == end.line) + return end.column - begin.column; + + int index = 0; + int currentLine = 1; + while (currentLine < begin.line) { + index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; + ++index; + ++currentLine; + } + const int beginIndex = index + begin.column; + while (currentLine < end.line) { + index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; + ++index; + ++currentLine; + } + return index + end.column - beginIndex; +} + +bool Range::operator==(const Range &other) const +{ + return begin == other.begin && end == other.end; +} + +QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset) +{ + const int lineStartUtf8Offset = currentUtf8Offset + ? (utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1) + : 0; + const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset); + return QString::fromUtf8( + utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset)); +} + +static bool isByteOfMultiByteCodePoint(unsigned char byte) +{ + return byte & 0x80; // Check if most significant bit is set +} + +bool utf8AdvanceCodePoint(const char *¤t) +{ + if (Q_UNLIKELY(*current == '\0')) + return false; + + // Process multi-byte UTF-8 code point (non-latin1) + if (Q_UNLIKELY(isByteOfMultiByteCodePoint(*current))) { + unsigned trailingBytesCurrentCodePoint = 1; + for (unsigned char c = (*current) << 2; isByteOfMultiByteCodePoint(c); c <<= 1) + ++trailingBytesCurrentCodePoint; + current += trailingBytesCurrentCodePoint + 1; + + // Process single-byte UTF-8 code point (latin1) + } else { + ++current; + } + + return true; +} + +QDebug &operator<<(QDebug &stream, const Position &pos) +{ + stream << "line: " << pos.line << ", column: " << pos.column; + return stream; +} + +} // namespace Utils::Text diff --git a/src/shared/lsp/textutils.h b/src/shared/lsp/textutils.h new file mode 100644 index 000000000..bf4ae083f --- /dev/null +++ b/src/shared/lsp/textutils.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QMetaType> +#include <QString> + +namespace lsp::Utils::Text { + +class Position +{ +public: + int line = 0; // 1-based + int column = -1; // 0-based + + bool operator<(const Position &other) const + { return line < other.line || (line == other.line && column < other.column); } + bool operator==(const Position &other) const; + + bool operator!=(const Position &other) const { return !(operator==(other)); } + + bool isValid() const { return line > 0 && column >= 0; } + + static Position fromFileName(QStringView fileName, int &postfixPos); +}; + +class Range +{ +public: + int length(const QString &text) const; + + Position begin; + Position end; + + bool operator<(const Range &other) const { return begin < other.begin; } + bool operator==(const Range &other) const; + + bool operator!=(const Range &other) const { return !(operator==(other)); } +}; + +QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset); + +QDebug &operator<<(QDebug &stream, const Position &pos); + +} // Text diff --git a/src/shared/lsp/workspace.cpp b/src/shared/lsp/workspace.cpp new file mode 100644 index 000000000..e653bf96b --- /dev/null +++ b/src/shared/lsp/workspace.cpp @@ -0,0 +1,82 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "workspace.h" + +namespace lsp { + +constexpr const char WorkSpaceFolderRequest::methodName[]; +constexpr const char DidChangeWorkspaceFoldersNotification::methodName[]; +constexpr const char DidChangeConfigurationNotification::methodName[]; +constexpr const char ConfigurationRequest::methodName[]; +constexpr const char WorkspaceSymbolRequest::methodName[]; +constexpr const char ExecuteCommandRequest::methodName[]; +constexpr const char ApplyWorkspaceEditRequest::methodName[]; +constexpr const char DidChangeWatchedFilesNotification::methodName[]; + +WorkSpaceFolderRequest::WorkSpaceFolderRequest() + : Request(methodName, nullptr) +{ } + +DidChangeWorkspaceFoldersNotification::DidChangeWorkspaceFoldersNotification( + const DidChangeWorkspaceFoldersParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeConfigurationNotification::DidChangeConfigurationNotification( + const DidChangeConfigurationParams ¶ms) + : Notification(methodName, params) +{ } + +ConfigurationRequest::ConfigurationRequest(const ConfigurationParams ¶ms) + : Request(methodName, params) +{ } + +WorkspaceSymbolRequest::WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms) + : Request(methodName, params) +{ } + +ExecuteCommandRequest::ExecuteCommandRequest(const ExecuteCommandParams ¶ms) + : Request(methodName, params) +{ } + +ApplyWorkspaceEditRequest::ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams ¶ms) + : Request(methodName, params) +{ } + +WorkspaceFoldersChangeEvent::WorkspaceFoldersChangeEvent() +{ + insert(addedKey, QJsonArray()); + insert(removedKey, QJsonArray()); +} + +DidChangeWatchedFilesNotification::DidChangeWatchedFilesNotification( + const DidChangeWatchedFilesParams ¶ms) + : Notification(methodName, params) +{ } + +ExecuteCommandParams::ExecuteCommandParams(const Command &command) +{ + setCommand(command.command()); + if (command.arguments().has_value()) + setArguments(*command.arguments()); +} + +WorkSpaceFolderResult::operator const QJsonValue() const +{ + if (!std::holds_alternative<QList<WorkSpaceFolder>>(*this)) + return QJsonValue::Null; + QJsonArray array; + for (const auto &folder : std::get<QList<WorkSpaceFolder>>(*this)) + array.append(QJsonValue(folder)); + return array; +} + +std::optional<DocumentUri> ConfigurationParams::ConfigurationItem::scopeUri() const +{ + if (const std::optional<QString> optionalScope = optionalValue<QString>(scopeUriKey)) + return std::make_optional(DocumentUri::fromProtocol(*optionalScope)); + return std::nullopt; +} + +} // namespace lsp diff --git a/src/shared/lsp/workspace.h b/src/shared/lsp/workspace.h new file mode 100644 index 000000000..ffd37c1e6 --- /dev/null +++ b/src/shared/lsp/workspace.h @@ -0,0 +1,245 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "jsonrpcmessages.h" + +namespace lsp { + +class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolderResult + : public std::variant<QList<WorkSpaceFolder>, std::nullptr_t> +{ +public: + using variant::variant; + using variant::operator=; + operator const QJsonValue() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolderRequest : public Request< + WorkSpaceFolderResult, std::nullptr_t, std::nullptr_t> +{ +public: + WorkSpaceFolderRequest(); + using Request::Request; + constexpr static const char methodName[] = "workspace/workspaceFolders"; + + bool parametersAreValid(QString * /*errorMessage*/) const override { return true; } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersChangeEvent : public JsonObject +{ +public: + using JsonObject::JsonObject; + WorkspaceFoldersChangeEvent(); + + QList<WorkSpaceFolder> added() const { return array<WorkSpaceFolder>(addedKey); } + void setAdded(const QList<WorkSpaceFolder> &added) { insertArray(addedKey, added); } + + QList<WorkSpaceFolder> removed() const { return array<WorkSpaceFolder>(removedKey); } + void setRemoved(const QList<WorkSpaceFolder> &removed) { insertArray(removedKey, removed); } + + bool isValid() const override { return contains(addedKey) && contains(removedKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + WorkspaceFoldersChangeEvent event() const + { return typedValue<WorkspaceFoldersChangeEvent>(eventKey); } + void setEvent(const WorkspaceFoldersChangeEvent &event) { insert(eventKey, event); } + + bool isValid() const override { return contains(eventKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersNotification : public Notification< + DidChangeWorkspaceFoldersParams> +{ +public: + explicit DidChangeWorkspaceFoldersNotification(const DidChangeWorkspaceFoldersParams ¶ms); + constexpr static const char methodName[] = "workspace/didChangeWorkspaceFolders"; + using Notification::Notification; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QJsonValue settings() const { return value(settingsKey); } + void setSettings(QJsonValue settings) { insert(settingsKey, settings); } + + bool isValid() const override { return contains(settingsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationNotification : public Notification< + DidChangeConfigurationParams> +{ +public: + explicit DidChangeConfigurationNotification(const DidChangeConfigurationParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "workspace/didChangeConfiguration"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationItem : public JsonObject + { + public: + using JsonObject::JsonObject; + + std::optional<DocumentUri> scopeUri() const; + void setScopeUri(const DocumentUri &scopeUri) { insert(scopeUriKey, scopeUri); } + void clearScopeUri() { remove(scopeUriKey); } + + std::optional<QString> section() const { return optionalValue<QString>(sectionKey); } + void setSection(const QString §ion) { insert(sectionKey, section); } + void clearSection() { remove(sectionKey); } + + bool isValid() const override { return contains(scopeUriKey); } + }; + + QList<ConfigurationItem> items() const { return array<ConfigurationItem>(itemsKey); } + void setItems(const QList<ConfigurationItem> &items) { insertArray(itemsKey, items); } + + bool isValid() const override { return contains(itemsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationRequest + : public Request<QJsonArray, std::nullptr_t, ConfigurationParams> +{ +public: + explicit ConfigurationRequest(const ConfigurationParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "workspace/configuration"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + class FileEvent : public JsonObject + { + public: + using JsonObject::JsonObject; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + int type() const { return typedValue<int>(typeKey); } + void setType(int type) { insert(typeKey, type); } + + enum FileChangeType { + Created = 1, + Changed = 2, + Deleted = 3 + }; + + bool isValid() const override { return contains(uriKey) && contains(typeKey); } + }; + + QList<FileEvent> changes() const { return array<FileEvent>(changesKey); } + void setChanges(const QList<FileEvent> &changes) { insertArray(changesKey, changes); } + + bool isValid() const override { return contains(changesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesNotification : public Notification< + DidChangeWatchedFilesParams> +{ +public: + explicit DidChangeWatchedFilesNotification(const DidChangeWatchedFilesParams ¶ms); + using Notification::Notification; + constexpr static const char methodName[] = "workspace/didChangeWatchedFiles"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString query() const { return typedValue<QString>(queryKey); } + void setQuery(const QString &query) { insert(queryKey, query); } + + void setLimit(int limit) { insert("limit", limit); } // clangd extension + + bool isValid() const override { return contains(queryKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request< + LanguageClientArray<SymbolInformation>, std::nullptr_t, WorkspaceSymbolParams> +{ +public: + explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "workspace/symbol"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandParams : public JsonObject +{ +public: + explicit ExecuteCommandParams(const Command &command); + explicit ExecuteCommandParams(const QJsonValue &value) : JsonObject(value) {} + ExecuteCommandParams() : JsonObject() {} + + QString command() const { return typedValue<QString>(commandKey); } + void setCommand(const QString &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + std::optional<QJsonArray> arguments() const { return typedValue<QJsonArray>(argumentsKey); } + void setArguments(const QJsonArray &arguments) { insert(argumentsKey, arguments); } + void clearArguments() { remove(argumentsKey); } + + bool isValid() const override { return contains(commandKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandRequest : public Request< + QJsonValue, std::nullptr_t, ExecuteCommandParams> +{ +public: + explicit ExecuteCommandRequest(const ExecuteCommandParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "workspace/executeCommand"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + std::optional<QString> label() const { return optionalValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + void clearLabel() { remove(labelKey); } + + WorkspaceEdit edit() const { return typedValue<WorkspaceEdit>(editKey); } + void setEdit(const WorkspaceEdit &edit) { insert(editKey, edit); } + + bool isValid() const override { return contains(editKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditResult : public JsonObject +{ +public: + using JsonObject::JsonObject; + + bool applied() const { return typedValue<bool>(appliedKey); } + void setApplied(bool applied) { insert(appliedKey, applied); } + + bool isValid() const override { return contains(appliedKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditRequest : public Request< + ApplyWorkspaceEditResult, std::nullptr_t, ApplyWorkspaceEditParams> +{ +public: + explicit ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams ¶ms); + using Request::Request; + constexpr static const char methodName[] = "workspace/applyEdit"; +}; + +} // namespace LanguageClient |