From 0827efb153901b2376bd8dbbbe3d161a148cd81a Mon Sep 17 00:00:00 2001 From: Oleg <2695198+heilgar@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:42:19 -0400 Subject: [PATCH] add auto completion --- CHANGELOG.md | 13 + README.md | 342 ++++----------- doc/autocompletion.md | 85 ++++ doc/configuration.md | 131 ++++++ doc/environments.md | 128 ++++++ doc/http_client.txt | 28 ++ doc/profiling.md | 89 ++++ doc/response-handling.md | 114 +++++ doc/telescope-integration.md | 74 ++++ lua/http_client/completion.lua | 630 +++++++++++++++++++++++++++ lua/http_client/core/environment.lua | 5 + lua/http_client/health.lua | 20 + lua/http_client/init.lua | 12 + 13 files changed, 1416 insertions(+), 255 deletions(-) create mode 100644 doc/autocompletion.md create mode 100644 doc/configuration.md create mode 100644 doc/environments.md create mode 100644 doc/profiling.md create mode 100644 doc/response-handling.md create mode 100644 doc/telescope-integration.md create mode 100644 lua/http_client/completion.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 099e35b..0713553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] 2025-05-01 +### Added +* Intelligent Autocompletion System + * Environment variable completion with `{{` trigger + * HTTP method completion at the start of requests + * HTTP header name completion with documentation + * Content type suggestions for Content-Type and Accept headers + * Adaptive filtering for better matching as you type + * Robust support for nvim-cmp + * Fallback completion for vanilla Neovim + * Caching system for recently used variables + * Context-aware completion based on cursor position + ## [1.3.0] 2025-04-03 ### Added * Request Profiling Capabilities diff --git a/README.md b/README.md index 61f8362..8c19693 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,19 @@ A Neovim plugin for running HTTP requests directly from .http files, with suppor The core goal is to ensure compatibility with .http files from IntelliJ or VSCode, allowing them to run smoothly in Neovim and vice-versa. - **Development is ongoing, with new features added as needed or when time permits.** ## Table of Contents - [Features](#features) -- [Installation and configuration](#installation) +- [Installation](#installation) - [Usage](#usage) - - [Commands](#commands) - - [Keybindings](#keybindings) - - [Response handler](#response-handler) - - [Request Profiling](#request-profiling) -- [Telescope Integration](#telescope-integration) + - [Commands](#commands) + - [Keybindings](#keybindings) - [Documentation](#documentation) -- [Examples](#examples) - - [Environment Files](#environment-files) +- [Screenshots](#screenshots) - [Contributing](#contributing) - [License](#license) -- [Screenshots](#screenshots) - - [Dry Run](#dry-run) - - [Environment File Selection](#environment-file-selection) - - [Environment Selection](#environment-selection) - - [HTTP Response](#http-response) ## Features @@ -43,70 +33,67 @@ The core goal is to ensure compatibility with .http files from IntelliJ or VSCod - Dry run capability for request inspection - Request profiling with detailed timing metrics - Telescope integration for environment selection +- Autocompletion for HTTP methods, headers and environment variables - Compatible with [JetBrains HTTP Client](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html) and [VSCode Restclient](https://github.com/Huachao/vscode-restclient) -## Installation and Configuration +## Installation This plugin is designed to be installed with [Lazy.nvim](https://github.com/folke/lazy.nvim). -Add the following to your Neovim configuration: +### Quick Start (Copy & Paste) + +Copy this complete configuration into your Lazy.nvim setup: ```lua { - "heilgar/nvim-http-client", - dependencies = { - "nvim-lua/plenary.nvim", - }, - config = function() - require("http_client").setup({ - create_keybindings = false -- Disable default keybindings completely - }) - end, + "heilgar/nvim-http-client", + dependencies = { + "nvim-lua/plenary.nvim", + "hrsh7th/nvim-cmp", -- Optional but recommended for enhanced autocompletion + "nvim-telescope/telescope.nvim", -- Optional for better environment selection + }, + event = "VeryLazy", + ft = { "http", "rest" }, + config = function() + require("http_client").setup({ + -- Default configuration (works out of the box) + default_env_file = '.env.json', + request_timeout = 30000, + split_direction = "right", + create_keybindings = true, + + -- Profiling (timing metrics for requests) + profiling = { + enabled = true, + show_in_response = true, + detailed_metrics = true, + }, + + -- Default keybindings (can be customized) + keybindings = { + select_env_file = "hf", + set_env = "he", + run_request = "hr", + stop_request = "hx", + toggle_verbose = "hv", + toggle_profiling = "hp", + dry_run = "hd", + copy_curl = "hc", + save_response = "hs", + }, + }) + + -- Set up Telescope integration if available + if pcall(require, "telescope") then + require("telescope").load_extension("http_client") + end + end } ``` -This plugin can be configured automatically using the `after/plugin` directory. The default configuration is set in `after/plugin/http_client.lua`. You can override these settings by creating your own `after/plugin/http_client.lua` file in your Neovim configuration directory. - -Example configuration: - -```lua -if not pcall(require, "http_client") then - return -end - -local http_client = require("http_client") - -local config = { - default_env_file = '.env.json', - request_timeout = 30000, -- 30 seconds - profiling = { - enabled = true, - show_in_response = true, - detailed_metrics = true, - }, - keybindings = { - select_env_file = "hf", - set_env = "he", - run_request = "hr", - stop_request = "hs", - dry_run = "hd", - toggle_verbose = "hv", - toggle_profiling = "hp" - }, -} -http_client.setup(config) +> **Note about Autocompletion**: If you have nvim-cmp installed, HTTP-specific autocompletion sources are automatically registered and configured for `.http` and `.rest` files. You don't need any additional configuration! The plugin provides completion for HTTP methods, headers, environment variables, and content types out of the box. --- Set up Telescope integration if available -if pcall(require, "telescope") then - require("telescope").load_extension("http_client") - - -- Set up Telescope-specific keybindings - vim.api.nvim_set_keymap('n', 'hf', [[Telescope http_client http_env_files]], { noremap = true, silent = true }) - vim.api.nvim_set_keymap('n', 'he', [[Telescope http_client http_envs]], { noremap = true, silent = true }) -end -``` - -You can adjust these settings to your preferences. +For full configuration options, see [Configuration Documentation](doc/configuration.md). ## Usage @@ -114,207 +101,45 @@ You can adjust these settings to your preferences. 2. Create a `.env.json` file with your environments. 3. Use the provided commands to select an environment and run requests. - ### Commands -- `:HttpEnvFile`: Select an environment file (.env.json) to use. -- `:HttpEnv {env}`: Set the current environment to use (e.g., `:HttpEnv production`). -- `:HttpRun`: Run the HTTP request under the cursor. -- `:HttpRunAll`: Run all HTTP requests in the current file. -- `:HttpStop`: Stop the currently running HTTP request. -- `:HttpVerbose`: Toggle verbose mode for debugging. -- `:HttpProfiling`: Toggle request profiling. -- `:HttpDryRun`: Perform a dry run of the request under the cursor. -- `:HttpCopyCurl`: Copy the curl command for the HTTP request under the cursor. - -### Response Window -Responses are displayed in a dedicated split window with the following features: -- Each response creates a new tab with timestamp -- Latest 10 responses are preserved -- Navigation: - - `H` - Previous response - - `L` - Next response - - `q` or `` - Close response window +- `:HttpEnvFile`: Select an environment file (.env.json) to use +- `:HttpEnv`: Select an environment from the current environment file +- `:HttpRun`: Run the HTTP request under the cursor +- `:HttpRunAll`: Run all HTTP requests in the current file +- `:HttpStop`: Stop the currently running HTTP request +- `:HttpVerbose`: Toggle verbose mode for debugging +- `:HttpProfiling`: Toggle request profiling +- `:HttpDryRun`: Perform a dry run of the request under the cursor +- `:HttpCopyCurl`: Copy the curl command for the HTTP request under the cursor +- `:HttpSaveResponse`: Save the response body to a file ### Keybindings -The plugin comes with the following default keybindings: +The plugin comes with the following default keybindings (if `create_keybindings` is set to `true`): -- `he`: Select environment file -- `hs`: Set current environment +- `hf`: Select environment file +- `he`: Set current environment - `hr`: Run HTTP request under cursor - `hx`: Stop running HTTP request - `hv`: Toggle verbose mode - `hp`: Toggle request profiling - `hd`: Perform dry run -- `hc`: Copy curl command for HTTP request under cursor - -To customize these keybindings, you can add the following to your Neovim configuration: - -```lua -{ - "heilgar/nvim-http-client", - dependencies = { - "nvim-lua/plenary.nvim", - }, - config = function() - require("http_client").setup() - end, - keys = { - { "he", "HttpEnvFile", desc = "Select HTTP environment file" }, - { "hs", "HttpEnv", desc = "Set HTTP environment" }, - { "hr", "HttpRun", desc = "Run HTTP request" }, - { "hx", "HttpStop", desc = "Stop HTTP request" }, - { "hv", "HttpVerbose", desc = "Toggle verbose mode" }, - { "hp", "HttpProfiling", desc = "Toggle request profiling" }, - { "hd", "HttpDryRun", desc = "Perform dry run" }, - }, - cmd = { - "HttpEnvFile", - "HttpEnv", - "HttpRun", - "HttpStop", - "HttpVerbose", - "HttpProfiling", - "HttpDryRun" - }, -} -``` - -You can change the key mappings by modifying the `keybindings` table in the setup function and updating the `keys` table accordingly. - -### Response Handler -```http -### Set Token -POST {{base_url}}/login -Content-Type: application/json - -{ - "username": "{{username}}", - "password": "{{password}}" -} - -> {% -client.global.set("auth_token", response.body.token); -%} - -### Get Protected Resource -GET {{base_url}}/protected -Authorization: Bearer {{auth_token}} -``` - -### Request Profiling - -The plugin includes request profiling capabilities that show you detailed timing metrics for your HTTP requests. - -#### Configuration - -You can configure profiling in your setup: - -```lua -profiling = { - enabled = true, -- Enable or disable profiling - show_in_response = true, -- Show timing metrics in response output - detailed_metrics = true, -- Show detailed breakdown of timings -}, -``` - -#### Viewing Timing Metrics - -When profiling is enabled, the response window will include a "Timing" section that shows: - -- Total request time -- DNS resolution time -- TCP connection time -- TLS handshake time (for HTTPS requests) -- Request sending time -- Content transfer time - -#### Commands and Keybindings - -- `:HttpProfiling` - Toggle profiling on/off -- `hp` - Default keybinding to toggle profiling - -### Running Without Environment -You can now run requests without selecting an environment file. If environment variables are needed but not set, the plugin will display a message suggesting to select an environment file or set properties via a response handler. - -Dry runs can be executed even if no environment file is selected. In this case, a warning will be displayed in the dry run output if environment variables are needed but not set. - -## Telescope Integration - -This plugin can be integrated with Telescope for a more interactive experience in selecting environment files and environments. To use the Telescope integration: - -1. Make sure you have Telescope installed and configured. -2. Load the extension in your Neovim configuration: - -```lua -require('telescope').load_extension('http_client') -``` - -3. Use the following commands to access HTTP Client features through Telescope: - -- `:Telescope http_client http_env_files`: Browse and select HTTP environment files -- `:Telescope http_client http_envs`: Browse and select HTTP environments - -You can also create key mappings for these commands: - -```lua -vim.api.nvim_set_keymap('n', 'hf', [[Telescope http_client http_env_files]], { noremap = true, silent = true }) -vim.api.nvim_set_keymap('n', 'he', [[Telescope http_client http_envs]], { noremap = true, silent = true }) -``` - -Note: You need to select an environment file using `http_env_files` before you can select an environment using `http_envs`. +- `hc`: Copy curl command +- `hs`: Save response to file ## Documentation -After installing the plugin, you can access the full documentation by running `:h http_client` in Neovim. - -## Examples - -``` -GET {{host}}/api/users +For detailed documentation on specific features, see: -### +- [Configuration Guide](doc/configuration.md) - Complete configuration options +- [Environment Files and Variables](doc/environments.md) - Working with environments +- [Response Handling](doc/response-handling.md) - Response viewing and handling +- [Request Profiling](doc/profiling.md) - Performance profiling features +- [Telescope Integration](doc/telescope-integration.md) - Using with Telescope +- [Autocompletion](doc/autocompletion.md) - Autocompletion features -POST {{host}}/api/users -Content-Type: application/json - -{ - "name": "John Doe", - "email": "john@example.com" -} -``` - -### Environment Files - -Environment files (.env.json) allow you to define different sets of variables for your HTTP requests. The plugin will look for these files in your project root directory. - -The `*default` environment is used as a base, and other environments will override its values. - -```json -{ - "*default": { - "host": "http://localhost:3000" - }, - "production": { - "host": "https://api.example.com" - } -} -``` - -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -1. Read our [Contributing Guidelines](.github/CONTRIBUTING.md). -2. Ensure you've updated the `CHANGELOG.md` file with your changes. - - - - -## License - -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +You can also access the plugin's help documentation by running `:h http_client` in Neovim. ## Screenshots @@ -322,10 +147,6 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file ![Dry Run](doc/dry_run.png) This screenshot shows the dry run feature, which allows you to preview the HTTP request before sending it. -### Environment File Selection -![Environment File Selection](doc/env_f_select.png) -Here you can see the environment file selection process, where you choose the .env.json file to use for your requests. - ### Environment Selection ![Environment Selection](doc/env_select.png) This image demonstrates the environment selection within a chosen .env.json file, allowing you to switch between different configurations. @@ -334,3 +155,14 @@ This image demonstrates the environment selection within a chosen .env.json file ![HTTP Response](doc/response.png) This screenshot displays how HTTP responses are presented after executing a request. +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +1. Read our [Contributing Guidelines](.github/CONTRIBUTING.md). +2. Ensure you've updated the `CHANGELOG.md` file with your changes. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + diff --git a/doc/autocompletion.md b/doc/autocompletion.md new file mode 100644 index 0000000..aa0cef4 --- /dev/null +++ b/doc/autocompletion.md @@ -0,0 +1,85 @@ +# Autocompletion + +nvim-http-client provides autocompletion for HTTP requests using two methods: + +## Method 1: With nvim-cmp (Recommended) + +For the best experience, [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) is recommended. The plugin will automatically detect and register completion sources with nvim-cmp if available. + +With nvim-cmp, you'll get: +- Automatic suggestions as you type (LSP-like experience) +- HTTP methods completion at the start of a line +- HTTP headers completion with descriptions +- Environment variables completion when typing `{{` +- Content-Type values completion + +To set up nvim-cmp with nvim-http-client: + +1. Ensure you have nvim-cmp installed: +```lua +{ + "hrsh7th/nvim-cmp", + -- and other nvim-cmp dependencies +} +``` + +2. The plugin will automatically register its completion sources when loaded. You can customize the configuration for HTTP files: + +```lua +local cmp = require('cmp') +cmp.setup.filetype({ 'http', 'rest' }, { + sources = cmp.config.sources({ + { name = 'http_method' }, + { name = 'http_header' }, + { name = 'http_env_var' }, + { name = 'buffer' }, + }) +}) +``` + +## Method 2: Traditional Vim Completion (Fallback) + +If nvim-cmp is not available, the plugin will fall back to traditional Vim completion: + +- Type `{{` to trigger environment variable completion +- Press `` to manually trigger environment variable completion +- Press `` to manually trigger HTTP methods and headers completion +- When pressing Enter on a new line, header completion will be automatically triggered + +The fallback mode works well but lacks the smooth experience of nvim-cmp's automatic suggestions. + +## Completion Features + +The plugin provides the following completions: + +### 1. HTTP Method Completion +- GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, CONNECT +- Each method includes documentation explaining its purpose +- Automatically triggered at the beginning of a line + +### 2. HTTP Header Completion +- Common headers like Accept, Content-Type, Authorization, etc. +- Each header includes documentation +- Content-Type header provides additional MIME type completions +- Automatically triggered after method and URL + +### 3. Environment Variable Completion +- Variables from the current environment file +- Global variables set through response handlers +- Recently used variables (cached) +- Documentation showing the variable type and current value +- Triggered when typing `{{` + +### 4. Content Type Value Completion +- MIME types like application/json, application/xml, etc. +- Each with relevant documentation +- Triggered when typing a value for Content-Type header + +## Troubleshooting + +If you're experiencing issues with autocompletion: + +1. Ensure nvim-cmp is properly installed and configured +2. Check that the plugin has loaded properly +3. Verify the file is recognized as `http` or `rest` filetype +4. Try using `:checkhealth http_client` to diagnose any issues \ No newline at end of file diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 0000000..9d894c7 --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,131 @@ +# Configuration + +This document explains the configuration options for nvim-http-client. + +## Basic Configuration + +The plugin can be configured using the `setup` function: + +```lua +require("http_client").setup({ + -- Your configuration options here +}) +``` + +## Full Configuration Example + +Here's a complete configuration example using Lazy.nvim that you can copy and paste directly into your config: + +```lua +{ + "heilgar/nvim-http-client", + dependencies = { + "nvim-lua/plenary.nvim", + "hrsh7th/nvim-cmp" -- Optional but recommended for enhanced autocompletion + }, + event = "VeryLazy", + ft = { "http", "rest" }, + keys = { + { "he", "HttpEnvFile", desc = "Select HTTP environment file" }, + { "hs", "HttpSaveResponse", desc = "Save HttpResponse to file" }, + { "hr", "HttpRun", desc = "Run HTTP request" }, + { "hx", "HttpStop", desc = "Stop HTTP request" }, + { "hd", "HttpDryRun", desc = "DryRun HTTP request" }, + { "hv", "HttpVerbose", desc = "Toggle verbose for HTTP request" }, + { "ha", function() vim.cmd("HttpRunAll") end, desc = "Run all HTTP requests" }, + { "hf", "Telescope http_client http_env_files", desc = "Select HTTP env file (Telescope)" }, + { "hh", "Telescope http_client http_envs", desc = "Select HTTP env (Telescope)" }, + { "hp", "HttpProfiling", desc = "Toggle HttpProfiling request profiling" }, + { "hc", "HttpCopyCurl", desc = "Copy curl command for HTTP request" }, + }, + cmd = { + "HttpEnvFile", + "HttpEnv", + "HttpRun", + "HttpRunAll", + "HttpStop", + "HttpVerbose", + "HttpDryRun", + "HttpProfiling", + "HttpCopyCurl", + "HttpSaveResponse" + }, + config = function() + local http_client = require("http_client") + http_client.setup({ + -- Default environment file to use + default_env_file = '.env.json', + + -- Request timeout in milliseconds + request_timeout = 30000, -- 30 seconds + + -- Split direction for response window ('right', 'left', 'top', 'bottom') + split_direction = "right", + + -- Whether to create default keybindings (set to false when defining your own) + create_keybindings = false, + + -- Profiling configuration + profiling = { + -- Enable request profiling + enabled = true, + -- Show timing metrics in response output + show_in_response = true, + -- Show detailed breakdown of timing metrics + detailed_metrics = true, + }, + }) + + -- Set up Telescope integration + if pcall(require, "telescope") then + require("telescope").load_extension("http_client") + end + + -- Configure nvim-cmp for HTTP files + if pcall(require, "cmp") then + local cmp = require("cmp") + cmp.setup.filetype({ "http", "rest" }, { + sources = cmp.config.sources({ + { name = "http_method" }, -- HTTP methods (GET, POST, etc.) + { name = "http_header" }, -- HTTP headers + { name = "http_env_var" }, -- Environment variables + { name = "buffer" }, -- Buffer text for general completions + }) + }) + end + end, +} +``` + +## Configuration Options + +Here are all the configuration options available: + +### General Options + +- `default_env_file` (string): Default environment file to use (default: '.env.json') +- `request_timeout` (number): Request timeout in milliseconds (default: 30000) +- `split_direction` (string): Direction to open response window ('right', 'left', 'top', 'bottom') (default: 'right') +- `create_keybindings` (boolean): Whether to create default keybindings (default: true) + +### Profiling Options + +- `profiling.enabled` (boolean): Enable request profiling (default: true) +- `profiling.show_in_response` (boolean): Show timing metrics in response output (default: true) +- `profiling.detailed_metrics` (boolean): Show detailed breakdown of timing metrics (default: true) + +### Keybindings Options + +If `create_keybindings` is true, the following keybindings will be created: + +- `keybindings.select_env_file` (string): Keybinding to select environment file (default: "hf") +- `keybindings.set_env` (string): Keybinding to set current environment (default: "he") +- `keybindings.run_request` (string): Keybinding to run HTTP request under cursor (default: "hr") +- `keybindings.stop_request` (string): Keybinding to stop running HTTP request (default: "hx") +- `keybindings.dry_run` (string): Keybinding to perform dry run (default: "hd") +- `keybindings.toggle_verbose` (string): Keybinding to toggle verbose mode (default: "hv") +- `keybindings.copy_curl` (string): Keybinding to copy curl command (default: "hc") +- `keybindings.save_response` (string): Keybinding to save HTTP response (default: "hs") +- `keybindings.toggle_profiling` (string): Keybinding to toggle profiling (default: "hp") + +This plugin can also be configured using the `after/plugin` directory. Create a file at `after/plugin/http_client.lua` in your Neovim configuration directory to override the default settings. \ No newline at end of file diff --git a/doc/environments.md b/doc/environments.md new file mode 100644 index 0000000..8a81d20 --- /dev/null +++ b/doc/environments.md @@ -0,0 +1,128 @@ +# Environment Files and Variables + +nvim-http-client uses environment files to manage variables that can be used in your HTTP requests. + +## Environment Files + +Environment files are JSON files with the extension `.env.json`. The plugin will look for these files in your project directory. + +### Structure + +An environment file has the following structure: + +```json +{ + "*default": { + "host": "http://localhost:3000", + "apiKey": "dev-api-key" + }, + "production": { + "host": "https://api.example.com", + "apiKey": "prod-api-key" + }, + "staging": { + "host": "https://staging.example.com", + "apiKey": "staging-api-key" + } +} +``` + +- The `*default` environment is special and is used as a base environment. +- When you select an environment (e.g., "production"), it will inherit values from `*default` and then override them with the environment-specific values. + +### Private Environment Files + +For sensitive information, you can create a private environment file named `.private.env.json` which will be automatically loaded and merged with the regular environment file. + +```json +{ + "*default": { + "username": "admin", + "password": "secret" + }, + "production": { + "password": "prod-password" + } +} +``` + +Private environment files follow the same structure and inheritance rules as regular environment files. + +## Using Environment Variables + +You can use environment variables in your HTTP requests using the `{{variable}}` syntax: + +```http +### Get User +GET {{host}}/api/users/{{userId}} +Authorization: Bearer {{apiKey}} + +### Create User +POST {{host}}/api/users +Content-Type: application/json +Authorization: Bearer {{apiKey}} + +{ + "name": "John Doe", + "email": "john@example.com", + "password": "{{password}}" +} +``` + +Environment variables can be used in: +- URLs +- Headers +- Request bodies + +## Selecting Environments + +### Commands + +- `:HttpEnvFile`: Select an environment file to use (`.env.json`) +- `:HttpEnv`: Select an environment from the current environment file + +### With Telescope + +If you have the Telescope integration set up: + +- `:Telescope http_client http_env_files`: Select an environment file +- `:Telescope http_client http_envs`: Select an environment + +### Default Keybindings + +- `hf`: Select environment file +- `he`: Set current environment + +## Global Variables + +You can set global variables in your response handlers that will be available for all subsequent requests: + +```http +### Login +POST {{host}}/api/login +Content-Type: application/json + +{ + "username": "{{username}}", + "password": "{{password}}" +} + +> {% +client.global.set("token", response.body.token); +%} + +### Get Protected Resource +GET {{host}}/api/protected +Authorization: Bearer {{token}} +``` + +Global variables: +- Persist for the duration of your Neovim session +- Take precedence over environment variables +- Can be used just like environment variables + +## Running Without Environment + +You can run requests without selecting an environment file, but if your request uses environment variables, the plugin will display a message suggesting to select an environment file. + +For dry runs, you'll see a warning in the output if environment variables are needed but not set. \ No newline at end of file diff --git a/doc/http_client.txt b/doc/http_client.txt index 26e08cf..95da3ee 100644 --- a/doc/http_client.txt +++ b/doc/http_client.txt @@ -260,6 +260,34 @@ Dry runs can be executed even if no environment file is selected. In this case, a warning will be displayed in the dry run output if environment variables are needed but not set. +------------------------------------------------------------------------------ +AUTOCOMPLETION FEATURES *http_client-autocompletion* + +The plugin provides smart autocompletion features for .http and .rest files +to improve the request writing experience: + +Environment Variable Completion *http_client-env-completion* + Type `{{` to trigger a list of available environment variables. As you type, + the completion list filters to match your input. Variables include: + - Environment variables from the current environment + - Global variables set by response handlers + - Recently used variables (persistent across files) + +HTTP Method Completion *http_client-method-completion* + At the beginning of a new request (after a ### divider or blank line), + press to see available HTTP methods with documentation. + +HTTP Header Completion *http_client-header-completion* + When starting a new line in a request, press to get + completions for common HTTP headers with documentation. + +Content-Type Completion *http_client-content-type-completion* + After typing `Content-Type:` or `Accept:`, press to get + a list of common MIME types with their descriptions. + +If you have nvim-cmp installed, these completions will be automatically +integrated with it for a better user experience. + ------------------------------------------------------------------------------ COMMENTS *http_client-comments* diff --git a/doc/profiling.md b/doc/profiling.md new file mode 100644 index 0000000..21df1ee --- /dev/null +++ b/doc/profiling.md @@ -0,0 +1,89 @@ +# Request Profiling + +The nvim-http-client plugin includes request profiling capabilities that show you detailed timing metrics for your HTTP requests. + +## Overview + +Profiling helps you understand the performance of your HTTP requests by providing timing information for each stage of the request lifecycle: + +- DNS resolution time +- TCP connection time +- TLS handshake time (for HTTPS requests) +- Request sending time +- Server processing time +- Content transfer time +- Total request time + +## Configuration + +You can configure profiling in your setup: + +```lua +profiling = { + enabled = true, -- Enable or disable profiling + show_in_response = true, -- Show timing metrics in response output + detailed_metrics = true, -- Show detailed breakdown of timings +}, +``` + +### Options + +- `enabled`: Turn profiling on or off globally (default: true) +- `show_in_response`: Include timing metrics in the response window (default: true) +- `detailed_metrics`: Show a detailed breakdown of each timing component (default: true) + +## Using Profiling + +### Commands + +The plugin provides a command to toggle profiling on or off: + +- `:HttpProfiling` - Toggle profiling on/off + +### Keybindings + +The default keybinding to toggle profiling is: + +- `hp` - Toggle profiling (if `create_keybindings` is true) + +You can customize this in your configuration: + +```lua +keybindings = { + toggle_profiling = "hp", +} +``` + +## Viewing Timing Metrics + +When profiling is enabled, the response window will include a "Timing" section that shows: + +``` +Timing: + Total: 1023.45 ms + DNS Resolution: 12.34 ms + TCP Connection: 56.78 ms + TLS Handshake: 89.01 ms + Request Sending: 23.45 ms + Server Processing: 789.01 ms + Content Transfer: 52.86 ms +``` + +This helps you identify bottlenecks in your HTTP requests and understand where time is being spent during request execution. + +## Interpreting Results + +- **Total**: The complete time from request initiation to response completion +- **DNS Resolution**: Time to resolve the domain name to an IP address +- **TCP Connection**: Time to establish a TCP connection to the server +- **TLS Handshake**: Time to complete the TLS/SSL handshake (HTTPS only) +- **Request Sending**: Time to send the HTTP request headers and body +- **Server Processing**: Time the server takes to process the request and generate a response +- **Content Transfer**: Time to download the response body + +High values in specific areas can help you diagnose issues: + +- High DNS resolution time may indicate DNS server issues +- High connection time could suggest network latency +- High server processing time indicates server-side performance issues +- High content transfer time might mean large response payloads or bandwidth limitations \ No newline at end of file diff --git a/doc/response-handling.md b/doc/response-handling.md new file mode 100644 index 0000000..5fe746e --- /dev/null +++ b/doc/response-handling.md @@ -0,0 +1,114 @@ +# Response Handling + +nvim-http-client provides rich features for handling HTTP responses, including the ability to view, save, and process responses with custom handlers. + +## Response Window + +Responses are displayed in a dedicated split window with the following features: + +- Each response creates a new tab with timestamp +- Latest 10 responses are preserved +- Automatic formatting for JSON and XML responses +- Syntax highlighting for response bodies +- Timing metrics when profiling is enabled + +### Navigation + +You can navigate between responses using: + +- `H` - Previous response +- `L` - Next response +- `q` or `` - Close response window + +## Response Handlers + +Response handlers allow you to execute custom code after receiving a response. This is useful for extracting data from responses and using it in subsequent requests. + +### Syntax + +Response handlers are defined using the following syntax: + +```http +### Your HTTP Request +GET {{host}}/api/endpoint + +> {% +// Your JavaScript code here +%} +``` + +The code between `> {%` and `%}` is executed after the response is received. + +### Available Objects + +Within a response handler, you have access to: + +- `response` - The HTTP response object + - `response.body` - The response body (parsed as JSON if possible) + - `response.headers` - Response headers + - `response.status` - HTTP status code + +- `client` - The HTTP client object + - `client.global.set(key, value)` - Set a global variable for use in subsequent requests + +### Example: Extracting an Authentication Token + +```http +### Login +POST {{host}}/api/login +Content-Type: application/json + +{ + "username": "{{username}}", + "password": "{{password}}" +} + +> {% +// Extract the token from the response +const token = response.body.token; + +// Store it as a global variable +client.global.set("auth_token", token); + +// Log information (appears in response window) +console.log("Token extracted and stored as auth_token"); +%} + +### Get Protected Resource +GET {{host}}/api/protected +Authorization: Bearer {{auth_token}} +``` + +### Example: Processing Data + +```http +### Get Users +GET {{host}}/api/users + +> {% +// Count the number of users +const userCount = response.body.length; +client.global.set("userCount", userCount); + +// Find a specific user +const adminUser = response.body.find(user => user.role === 'admin'); +if (adminUser) { + client.global.set("adminId", adminUser.id); +} +%} + +### Get Admin Details +GET {{host}}/api/users/{{adminId}} +``` + +## Saving Responses + +You can save the current response body to a file using: + +- Command: `:HttpSaveResponse` +- Default keybinding: `hs` (if enabled) + +When saving a response: +1. You'll be prompted for a filename +2. The response body will be formatted (if it's JSON or XML) +3. The formatted content will be saved to the specified file \ No newline at end of file diff --git a/doc/telescope-integration.md b/doc/telescope-integration.md new file mode 100644 index 0000000..f433d86 --- /dev/null +++ b/doc/telescope-integration.md @@ -0,0 +1,74 @@ +# Telescope Integration + +nvim-http-client integrates with [Telescope](https://github.com/nvim-telescope/telescope.nvim) to provide a more interactive and visual way to select environment files and environments. + +## Setup + +To use the Telescope integration: + +1. Make sure you have Telescope installed and configured: + +```lua +{ + "nvim-telescope/telescope.nvim", + dependencies = { "nvim-lua/plenary.nvim" } +} +``` + +2. Load the extension in your Neovim configuration: + +```lua +require('telescope').load_extension('http_client') +``` + +This can be added to your plugin setup: + +```lua +config = function() + require("http_client").setup({ + -- your configuration + }) + + if pcall(require, "telescope") then + require("telescope").load_extension("http_client") + end +end +``` + +## Commands + +The plugin adds the following Telescope commands: + +- `:Telescope http_client http_env_files`: Browse and select HTTP environment files +- `:Telescope http_client http_envs`: Browse and select HTTP environments from the current environment file + +## Keybindings + +You can create keybindings for these commands in your configuration: + +```lua +-- With lazy.nvim +keys = { + { "hf", "Telescope http_client http_env_files", desc = "Select HTTP env file (Telescope)" }, + { "hh", "Telescope http_client http_envs", desc = "Select HTTP env (Telescope)" }, +} + +-- Or with direct keymaps +vim.api.nvim_set_keymap('n', 'hf', [[Telescope http_client http_env_files]], { noremap = true, silent = true }) +vim.api.nvim_set_keymap('n', 'he', [[Telescope http_client http_envs]], { noremap = true, silent = true }) +``` + +## Usage Flow + +1. Use `http_env_files` to first select which environment file you want to use (e.g., `.env.json`) +2. Then use `http_envs` to select the specific environment within that file (e.g., `production`, `staging`, etc.) + +Note: You need to select an environment file using `http_env_files` before you can select an environment using `http_envs`. + +## Features + +- Visual selection of environment files +- Preview of environment file contents +- Visual selection of environments within a file +- Preview of environment variables in each environment +- Seamless integration with the Telescope UI \ No newline at end of file diff --git a/lua/http_client/completion.lua b/lua/http_client/completion.lua new file mode 100644 index 0000000..bf4b194 --- /dev/null +++ b/lua/http_client/completion.lua @@ -0,0 +1,630 @@ +local M = {} +local environment = require('http_client.core.environment') + +-- Cache for recently used variables +local recent_vars = {} +local MAX_RECENT_VARS = 10 + +-- Common HTTP methods +local HTTP_METHODS = { + { label = "GET", documentation = "Retrieve data from the server" }, + { label = "POST", documentation = "Send data to the server to create a resource" }, + { label = "PUT", documentation = "Update an existing resource on the server" }, + { label = "DELETE", documentation = "Remove a resource from the server" }, + { label = "PATCH", documentation = "Partially update a resource" }, + { label = "HEAD", documentation = "Same as GET but without the response body" }, + { label = "OPTIONS", documentation = "Describe the communication options for the target resource" }, + { label = "TRACE", documentation = "Perform a message loop-back test along the path to the target resource" }, + { label = "CONNECT", documentation = "Establish a tunnel to the server identified by the target resource" } +} + +-- Common HTTP headers +local HTTP_HEADERS = { + { label = "Accept", documentation = "Media types that are acceptable for the response" }, + { label = "Accept-Charset", documentation = "Character sets that are acceptable" }, + { label = "Accept-Encoding", documentation = "List of acceptable encodings" }, + { label = "Accept-Language", documentation = "List of acceptable human languages for response" }, + { label = "Authorization", documentation = "Authentication credentials for HTTP authentication" }, + { label = "Cache-Control", documentation = "Directives for caching mechanisms in requests and responses" }, + { label = "Connection", documentation = "Control options for the current connection" }, + { label = "Content-Disposition", documentation = "Information about how the content should be presented" }, + { label = "Content-Encoding", documentation = "The type of encoding used on the data" }, + { label = "Content-Length", documentation = "The length of the request body in octets" }, + { label = "Content-Type", documentation = "The media type of the body of the request" }, + { label = "Cookie", documentation = "An HTTP cookie previously sent by the server" }, + { label = "Date", documentation = "The date and time that the message was sent" }, + { label = "Host", documentation = "The domain name of the server and the TCP port number" }, + { label = "Origin", documentation = "Initiates a request for cross-origin resource sharing" }, + { label = "Referer", documentation = "The address of the previous web page" }, + { label = "User-Agent", documentation = "The user agent string of the user agent" }, + { label = "X-Requested-With", documentation = "Mainly used to identify Ajax requests" } +} + +-- Common content types +local CONTENT_TYPES = { + { label = "application/json", documentation = "JSON data" }, + { label = "application/xml", documentation = "XML data" }, + { label = "application/x-www-form-urlencoded", documentation = "Form URL encoded data" }, + { label = "multipart/form-data", documentation = "Multipart form data, used for file uploads" }, + { label = "text/plain", documentation = "Plain text" }, + { label = "text/html", documentation = "HTML content" }, + { label = "text/css", documentation = "CSS content" }, + { label = "text/javascript", documentation = "JavaScript content" }, + { label = "application/octet-stream", documentation = "Binary data" } +} + +-- Add a variable to the recent vars cache +local function add_to_recent(var) + -- Remove if already in list + for i, v in ipairs(recent_vars) do + if v == var then + table.remove(recent_vars, i) + break + end + end + + -- Add to front + table.insert(recent_vars, 1, var) + + -- Trim to max size + if #recent_vars > MAX_RECENT_VARS then + table.remove(recent_vars) + end +end + +-- Get all available variables from current environment +local function get_env_variables() + local env = environment.get_current_env() or {} + local env_vars = {} + + -- Add environment variables + for key, _ in pairs(env) do + if type(key) == "string" and not key:match("^%*") then -- Skip environment metadata like *default + table.insert(env_vars, { + name = key, + source = "Environment", + value = tostring(env[key]) + }) + end + end + + -- Add global variables + local global_vars = environment.get_global_variables() or {} + for key, value in pairs(global_vars) do + if type(key) == "string" then + table.insert(env_vars, { + name = key, + source = "Global", + value = tostring(value) + }) + end + end + + -- Add recently used variables that aren't in the other lists + for _, var in ipairs(recent_vars) do + local exists = false + for _, env_var in ipairs(env_vars) do + if env_var.name == var then + exists = true + break + end + end + + if not exists then + table.insert(env_vars, { + name = var, + source = "Recent", + value = "Recently used" + }) + end + end + + return env_vars +end + +-- Check if nvim-cmp is available +local has_cmp = (function() + local ok, _ = pcall(require, "cmp") + return ok +end)() + +-- Track usage of environment variables in files +M.track_env_var_usage = function() + local bufnr = vim.api.nvim_get_current_buf() + + -- Get all lines in the buffer + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + + -- Find all environment variables in the buffer + for _, line in ipairs(lines) do + for var in line:gmatch("{{([^}]+)}}") do + var = var:match("^%s*(.-)%s*$") -- Trim whitespace + if var and var ~= "" then + add_to_recent(var) + end + end + end +end + +-- Legacy omnifunc for environment variables (used as fallback if nvim-cmp isn't available) +M.env_var_completion = function(_, _, _) + local line = vim.api.nvim_get_current_line() + local cursor_col = vim.api.nvim_win_get_cursor(0)[2] + + -- Only provide completions inside {{...}} + local before_cursor = line:sub(1, cursor_col) + local opens_count = 0 + local opens_pos = {} + + -- Find opening braces + for i = 1, #before_cursor - 1 do + if before_cursor:sub(i, i+1) == "{{" then + opens_count = opens_count + 1 + opens_pos[opens_count] = i + end + end + + -- Find closing braces + for i = 1, #before_cursor - 1 do + if before_cursor:sub(i, i+1) == "}}" then + opens_count = opens_count - 1 + if opens_count < 0 then + opens_count = 0 + end + end + end + + -- If cursor is not inside {{ }}, no completions + if opens_count == 0 then + return {} + end + + -- Extract the text between {{ and cursor + local last_open = opens_pos[opens_count] + local prefix = before_cursor:sub(last_open + 2):gsub("^%s+", "") -- Trim leading whitespace + + -- Get all env variables + local items = {} + local env_vars = get_env_variables() + + -- Filter variables by prefix + for _, var in ipairs(env_vars) do + if var.name:sub(1, #prefix) == prefix then + table.insert(items, { + label = var.name, + kind = vim.lsp.protocol.CompletionItemKind.Variable, + insertText = var.name, + documentation = string.format("%s variable: %s\nValue: %s", + var.source, var.name, var.value) + }) + + -- Add to recent on selection + if prefix == var.name then + add_to_recent(var.name) + end + end + end + + return { + items = items, + isIncomplete = true + } +end + +-- Legacy completefunc for HTTP methods and headers (used as fallback if nvim-cmp isn't available) +M.http_completion = function(findstart, base) + if findstart == 1 then + local line = vim.api.nvim_get_current_line() + local col = vim.api.nvim_win_get_cursor(0)[2] + + -- Check if we're at the start of the line - method completion + if col == 0 or line:sub(1, col):match("^%s*$") then + return 0 -- Complete from the start of the line + end + + -- Check if we're after a method - URL completion + if line:match("^%s*[A-Z]+%s+") and not line:match(":") then + local method_end = line:find("%s+") + if method_end and col >= method_end then + return method_end -- Complete from after the method + end + end + + -- Check if we're at header name position + local start_col = line:find("^%s*[%w%-]+:?%s*") + if start_col and not line:find(":", 1, true) then + return 0 -- Complete from the start of the line + end + + -- Check if we're completing a Content-Type or Accept value + if line:match("^%s*[Cc]ontent%-[Tt]ype:%s*") or line:match("^%s*[Aa]ccept:%s*") then + local colon_pos = line:find(":", 1, true) + if colon_pos and col > colon_pos then + return colon_pos + 1 -- Complete from after the colon + end + end + + return -1 -- No completion + else + local line = vim.api.nvim_get_current_line() + local col = vim.api.nvim_win_get_cursor(0)[2] + local items = {} + + -- Method completion + if line == "" or line:match("^%s*$") or (col == 0 and base == "") then + for _, method in ipairs(HTTP_METHODS) do + table.insert(items, { + word = method.label, + kind = "Method", + info = method.documentation, + icase = 1, + dup = 0, + empty = 1 + }) + end + return items + end + + -- Content-Type or Accept value completion + if line:match("^%s*[Cc]ontent%-[Tt]ype:%s*") or line:match("^%s*[Aa]ccept:%s*") then + for _, ct in ipairs(CONTENT_TYPES) do + if ct.label:lower():find(base:lower(), 1, true) == 1 or base == "" then + table.insert(items, { + word = ct.label, + kind = "Value", + info = ct.documentation, + icase = 1, + dup = 0, + empty = 1 + }) + end + end + return items + end + + -- Header completion + if line:match("^%s*$") or (not line:find(":", 1, true) and col <= #line) then + for _, header in ipairs(HTTP_HEADERS) do + if header.label:lower():find(base:lower(), 1, true) == 1 then + table.insert(items, { + word = header.label .. ": ", + kind = "Field", + info = header.documentation, + icase = 1, + dup = 0, + empty = 1 + }) + end + end + + return items + end + + return {} + end +end + +-- ------------------------------------------------ +-- nvim-cmp Source for HTTP Methods +-- ------------------------------------------------ +M.create_method_source = function() + return { + option = { + keyword_pattern = [[\w\+]], + }, + is_available = function() + return vim.bo.filetype == "http" or vim.bo.filetype == "rest" + end, + get_trigger_characters = function() + -- Add empty string to trigger at the beginning of a line + return { '' } + end, + get_keyword_pattern = function() + -- Make sure to match methods at the start of a line + return [[^\s*\zs\w\+\ze]] + end, + complete = function(_, request, callback) + local line = request.context.cursor_line + local cursor_col = request.context.cursor.col + local line_num = vim.api.nvim_win_get_cursor(0)[1] + + -- Only provide method completions at the start of a line + if cursor_col > 1 and not line:sub(1, cursor_col-1):match("^%s*$") then + callback({ items = {}, isIncomplete = false }) + return + end + + -- Check if we're in a good context for method completion: + -- 1. Previous line must be a ### divider, or empty line after a request + local valid_context = false + + if line_num == 1 then + valid_context = true -- First line of file is always valid + elseif line_num > 1 then + local prev_line = vim.api.nvim_buf_get_lines(0, line_num - 2, line_num - 1, false)[1] + if prev_line and prev_line:match("^%s*###") then + valid_context = true -- Line after the divider is valid for method + elseif prev_line and prev_line:match("^%s*$") then + -- Check if we're in a request body + local in_body = false + -- Look up at previous lines to see if we find a request line before empty line + for i = line_num - 2, math.max(line_num - 10, 1), -1 do + local check_line = vim.api.nvim_buf_get_lines(0, i - 1, i, false)[1] + if check_line and check_line:match("^%s*[A-Z]+%s+%S+") then + in_body = true + break + elseif check_line and check_line:match("^%s*###") then + -- Found divider before request, not in body + break + end + end + if not in_body then + valid_context = true -- Empty line not in request body + end + end + end + + if not valid_context then + callback({ items = {}, isIncomplete = false }) + return + end + + -- Check if we already have a method on this line (we shouldn't) + if line:match("^%s*[A-Z]+%s+%S+") then + callback({ items = {}, isIncomplete = false }) + return + end + + local items = {} + for _, method in ipairs(HTTP_METHODS) do + -- Make matching more flexible - case insensitive + local input_text = request.context.cursor_before_line:match("^%s*(.*)$") or "" + if input_text == "" or method.label:lower():find(input_text:lower(), 1, true) == 1 then + table.insert(items, { + label = method.label, + kind = 5, -- Function + documentation = { + kind = "markdown", + value = "**" .. method.label .. "**\n\n" .. method.documentation + }, + insertText = method.label .. " ", + }) + end + end + + callback({ items = items, isIncomplete = true }) + end + } +end + +-- ------------------------------------------------ +-- nvim-cmp Source for HTTP Headers +-- ------------------------------------------------ +M.create_header_source = function() + return { + option = { + keyword_pattern = [[\w\+]], + }, + is_available = function() + return vim.bo.filetype == "http" or vim.bo.filetype == "rest" + end, + get_trigger_characters = function() + -- Add triggers for content type suggestions after colon or space + return { 'A', 'C', 'H', 'U', 'a', 'c', 'h', 'u', ':', ' ', 'j', 'x', 'm', 't', 'p', 'l', 'f', 'o', 'r', 'm' } + end, + get_keyword_pattern = function() + -- Pattern for header names at the start of a line and values after the colon + return [[^\s*\zs\(\w\|-\)\+\ze:\|^\s*\(\w\|-\)\+:\s*\zs\S*\ze]] + end, + complete = function(_, request, callback) + local line = request.context.cursor_line + local cursor_col = request.context.cursor.col + local before_cursor = line:sub(1, cursor_col) + + -- Skip if we're in the first line (usually the request line with the HTTP method) + local line_num = vim.api.nvim_win_get_cursor(0)[1] + local is_first_line = (line_num == 1) + + -- Check for request line pattern (HTTP method + URL) + local is_request_line = line:match("^%s*[A-Z]+%s+%S+") ~= nil + + if is_first_line or is_request_line then + callback({ items = {}, isIncomplete = false }) + return + end + + local items = {} + + -- Extract header name and any partial value typed after the colon + local header_match = before_cursor:match("^%s*([^:]+):%s*(.*)$") + + -- If we detected a header with a colon (header value completion) + if header_match then + local header_name = header_match:match("^([^:]+)") + if not header_name then + header_name = "" + end + + header_name = header_name:lower():gsub("^%s+", ""):gsub("%s+$", "") + + -- Special handling for Content-Type or Accept headers + if header_name == "content-type" or header_name == "accept" then + -- Get whatever has been typed after the colon + local value_prefix = before_cursor:match("^%s*[^:]+:%s*(.*)$") or "" + value_prefix = value_prefix:lower():gsub("^%s+", "") + + for _, ct in ipairs(CONTENT_TYPES) do + if value_prefix == "" or ct.label:lower():find(value_prefix, 1, true) == 1 then + table.insert(items, { + label = ct.label, + kind = 12, -- Value + documentation = { + kind = "markdown", + value = "**" .. ct.label .. "**\n\n" .. ct.documentation + } + }) + end + end + + callback({ items = items, isIncomplete = true }) + return + end + + -- For other headers, no completions after the colon + callback({ items = {}, isIncomplete = false }) + return + end + + -- Header name completion (for headers without a colon yet) + -- This is for when user is typing a new header name at start of line + local current_input = before_cursor:match("^%s*(.*)$") or "" + current_input = current_input:lower() + + for _, header in ipairs(HTTP_HEADERS) do + if current_input == "" or header.label:lower():find(current_input, 1, true) == 1 then + table.insert(items, { + label = header.label, + kind = 6, -- Field + documentation = { + kind = "markdown", + value = "**" .. header.label .. "**\n\n" .. header.documentation + }, + insertText = header.label .. ": ", + }) + end + end + + callback({ items = items, isIncomplete = true }) + end + } +end + +-- ------------------------------------------------ +-- nvim-cmp Source for Environment Variables +-- ------------------------------------------------ +M.create_env_var_source = function() + return { + is_available = function() + return vim.bo.filetype == "http" or vim.bo.filetype == "rest" + end, + get_trigger_characters = function() + -- Trigger on every possible character that could be in a variable name + return { "{", "}", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "_" } + end, + get_keyword_pattern = function() + -- Match everything after {{ until }} + return [[{{[^}]*}}?]] + end, + complete = function(self, request, callback) + local cursor_before_line = request.context.cursor_before_line + + -- Check if we're typing inside {{ but before }} + local match_start, match_end = cursor_before_line:find("{{[^}]*$") + + if not match_start then + callback({ items = {}, isIncomplete = false }) + return + end + + -- Extract what user has typed after {{ + local prefix = cursor_before_line:sub(match_start + 2):gsub("^%s+", "") -- Trim leading whitespace + + local items = {} + local env_vars = get_env_variables() + + -- Collect all matching variables based on prefix + for _, var in ipairs(env_vars) do + -- Case insensitive match that shows variables starting with what's been typed + if prefix == "" or var.name:lower():find(prefix:lower(), 1, true) == 1 then + table.insert(items, { + label = var.name, + filterText = var.name, -- Ensure filtering uses the variable name + kind = 6, -- Variable + documentation = { + kind = "markdown", + value = string.format("**%s Variable**\n\nName: %s\nValue: %s", + var.source, var.name, var.value) + }, + insertText = var.name, + }) + + -- Add to recent variables cache when selected + if prefix == var.name then + add_to_recent(var.name) + end + end + end + + -- Force completion window to stay open while typing + callback({ + items = items, + isIncomplete = true + }) + end + } +end + +-- Setup function for registering completion source +M.setup = function() + -- Set up nvim-cmp sources if available + if has_cmp then + local cmp = require("cmp") + + -- Register HTTP Method source + cmp.register_source('http_method', M.create_method_source()) + + -- Register HTTP Header source + cmp.register_source('http_header', M.create_header_source()) + + -- Register HTTP Environment Variable source + cmp.register_source('http_env_var', M.create_env_var_source()) + + -- Add sources to filetype configuration + cmp.setup.filetype({ 'http', 'rest' }, { + sources = cmp.config.sources({ + { name = 'http_method' }, + { name = 'http_header' }, + { name = 'http_env_var' }, + { name = 'buffer' }, + }) + }) + end + + -- Fallback for when nvim-cmp isn't available: set up traditional Vim completion + vim.api.nvim_create_autocmd("FileType", { + pattern = {"http", "rest"}, + callback = function() + -- Set up omnifunc for env variable completion + vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.require("http_client.completion").env_var_completion') + + -- Set up completefunc for HTTP methods and headers + vim.api.nvim_buf_set_option(0, 'completefunc', 'v:lua.require("http_client.completion").http_completion') + + -- Track environment variable usage when the file is loaded + M.track_env_var_usage() + + -- Add triggers if nvim-cmp isn't available + if not has_cmp then + -- Auto-trigger environment variable completion + vim.keymap.set('i', '{{', '{{', { buffer = true, noremap = true, silent = true }) + + -- Auto-trigger header completion on newline + vim.keymap.set('i', '', '', { buffer = true, noremap = true, silent = true }) + end + end + }) + + -- Track environment variable usage when saving a file + vim.api.nvim_create_autocmd("BufWritePost", { + pattern = {"*.http", "*.rest"}, + callback = function() + M.track_env_var_usage() + end + }) +end + +return M \ No newline at end of file diff --git a/lua/http_client/core/environment.lua b/lua/http_client/core/environment.lua index 456c7fc..cf46713 100644 --- a/lua/http_client/core/environment.lua +++ b/lua/http_client/core/environment.lua @@ -104,6 +104,11 @@ M.get_global_variable = function(key) return global_variables[key] end +-- Return all global variables +M.get_global_variables = function() + return global_variables +end + M.env_variables_needed = function (request) local function check_for_placeholders(str) return str and str:match("{{.-}}") diff --git a/lua/http_client/health.lua b/lua/http_client/health.lua index 8123a37..b359d16 100644 --- a/lua/http_client/health.lua +++ b/lua/http_client/health.lua @@ -27,6 +27,26 @@ M.check = function() else health.warn("telescope.nvim is not installed", "Install telescope.nvim for enhanced environment selection") end + + -- Check for nvim-cmp integration + if pcall(require, "cmp") then + health.ok("nvim-cmp is installed") + + -- Check if our sources are registered + local cmp = require("cmp") + local source_available = false + + -- Try to access the source (we can't directly check if registered, but we can check if our files exist) + if pcall(require, "http_client.completion") then + health.ok("HTTP Client completion module is available") + health.info("Using enhanced nvim-cmp autocompletion") + else + health.warn("HTTP Client completion module is not properly loaded") + end + else + health.info("nvim-cmp not installed, using fallback completion methods") + health.info("For enhanced autocompletion, install nvim-cmp") + end -- Check if curl is available local curl_check = vim.fn.system("which curl") diff --git a/lua/http_client/init.lua b/lua/http_client/init.lua index 88dd6d9..2900cf6 100644 --- a/lua/http_client/init.lua +++ b/lua/http_client/init.lua @@ -53,6 +53,18 @@ M.setup = function(opts) M.dry_run = require("http_client.ui.dry_run") M.v = require("http_client.utils.verbose") M.commands = require("http_client.commands") + M.completion = require("http_client.completion") + + -- Initialize completion module + M.completion.setup() + + -- Set up filetype detection + vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, { + pattern = {"*.http", "*.rest"}, + callback = function() + vim.bo.filetype = "http" + end + }) -- Set up commands vim.api.nvim_create_user_command("HttpEnvFile", function()