├── haskell ├── .gitignore ├── src │ ├── Main.hs │ ├── HoogleQuery.hs │ ├── PangoUtils.hs │ └── HoogleQuery │ │ ├── SearchHoogle.hs │ │ ├── ResultSorting.hs │ │ └── Native.hs ├── CHANGELOG.md ├── shell.nix ├── csrc │ ├── wrapper.c │ └── rofi_hoogle_hs.h ├── default.nix ├── LICENSE └── rofi-hoogle.cabal ├── rofi-hoogle-plugin ├── .gitignore ├── src │ ├── .gitignore │ ├── Makefile │ └── plugin.c └── package.nix ├── .gitignore ├── flake.nix ├── release.nix ├── flake.lock └── README.md /haskell/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle 2 | -------------------------------------------------------------------------------- /rofi-hoogle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | result -------------------------------------------------------------------------------- /rofi-hoogle-plugin/src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | .ccls 3 | .ccls-cache 4 | .direnv 5 | .envrc 6 | -------------------------------------------------------------------------------- /haskell/src/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | import Hoogle 3 | 4 | main :: IO () 5 | main = putStrLn "Hello, Haskell!" 6 | -------------------------------------------------------------------------------- /haskell/src/HoogleQuery.hs: -------------------------------------------------------------------------------- 1 | module HoogleQuery 2 | ( module HoogleQuery.SearchHoogle 3 | ) where 4 | 5 | import HoogleQuery.SearchHoogle 6 | -------------------------------------------------------------------------------- /haskell/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for rofi-hoogle 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /haskell/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | let 4 | rofi-hoogle = pkgs.callPackage ./package.nix {}; 5 | in 6 | 7 | pkgs.mkShell { 8 | inputsFrom = [ rofi-hoogle.env ]; 9 | } 10 | -------------------------------------------------------------------------------- /haskell/csrc/wrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "HsFFI.h" 3 | 4 | void hs_hoogle_search_init(void) { 5 | int argc = 2; 6 | char* argv[] = { "+RTS", "-A64m", NULL }; 7 | char **pargv = argv; 8 | hs_init(&argc, &pargv); 9 | } 10 | 11 | void hs_hoogle_search_end(void) { hs_exit(); } 12 | -------------------------------------------------------------------------------- /haskell/src/PangoUtils.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ImportQualifiedPost #-} 2 | module PangoUtils where 3 | import HTMLEntities.Decoder 4 | import Debug.Trace 5 | import Data.Text qualified as Text 6 | import Data.Text.Lazy qualified as LazyText 7 | import Data.Text.Lazy.Builder qualified as Builder 8 | 9 | removeHTML :: String -> String 10 | removeHTML [] = [] 11 | removeHTML ('<':xs) = removeHTML . drop 1 . dropWhile (/='>') $ xs 12 | removeHTML (x:xs) = x : removeHTML xs 13 | 14 | cleanupHTML :: String -> String 15 | cleanupHTML = LazyText.unpack . Builder.toLazyText . htmlEncodedText . Text.pack . removeHTML 16 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A Rofi plugin for searching Hoogle"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = {self, nixpkgs, flake-utils }@inputs: 10 | flake-utils.lib.eachDefaultSystem(system: 11 | let 12 | pkgs = import nixpkgs {inherit system; }; 13 | hs-hoogle-query = pkgs.haskellPackages.callPackage ./haskell {}; 14 | rofi-hoogle = pkgs.callPackage ./rofi-hoogle-plugin/package.nix { inherit hs-hoogle-query; }; 15 | in { 16 | packages.hs-hoogle-query = hs-hoogle-query; 17 | packages.rofi-hoogle = rofi-hoogle; 18 | defaultPackage = self.packages.${system}.rofi-hoogle; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | let 2 | hs-hoogle-overlay = self: super: { 3 | hs-hoogle-query = super.haskellPackages.callPackage ./haskell {}; 4 | }; 5 | 6 | pkgs = import { overlays = [hs-hoogle-overlay]; }; 7 | 8 | hs-hoogle-query = pkgs.haskellPackages.callPackage ./haskell {}; 9 | rofi-hoogle = pkgs.callPackage ./rofi-hoogle-plugin/package.nix {}; 10 | 11 | cDevelopmentTools = with pkgs; [ 12 | gcc 13 | gdb 14 | valgrind 15 | binutils 16 | strace 17 | ltrace 18 | xxd 19 | pkg-config 20 | ]; 21 | 22 | testTools = with pkgs; [ 23 | rofi 24 | rofi-unwrapped 25 | ]; 26 | 27 | devShell = pkgs.mkShell { 28 | buildInputs = [ rofi-hoogle.buildInputs rofi-hoogle.nativeBuildInputs cDevelopmentTools testTools]; 29 | }; 30 | 31 | in { inherit rofi-hoogle; } 32 | -------------------------------------------------------------------------------- /rofi-hoogle-plugin/package.nix: -------------------------------------------------------------------------------- 1 | { pkgs 2 | , hs-hoogle-query 3 | , ... 4 | }: 5 | pkgs.stdenv.mkDerivation rec { 6 | pname = "rofi-hoogle-plugin"; 7 | version = "0.0.1"; 8 | src = ./src; 9 | 10 | nativeBuildInputs = with pkgs; [ 11 | pkg-config 12 | glib 13 | libnotify 14 | makeWrapper 15 | rofi-unwrapped 16 | hs-hoogle-query 17 | ]; 18 | 19 | buildInputs = with pkgs; [ 20 | glib 21 | libnotify 22 | makeWrapper 23 | rofi-unwrapped 24 | cairo 25 | hs-hoogle-query 26 | xdg-utils 27 | ]; 28 | 29 | installPhase = '' 30 | mkdir -p $out/lib/rofi 31 | make install -e INSTALL_ROOT=$out 32 | ''; 33 | 34 | meta = with pkgs.lib; { 35 | description = "Search Hoogle from Rofi"; 36 | homepage = "https://github.com/rebeccaskinner/rofi-hoogle"; 37 | license = licenses.bsd3; 38 | platforms = platforms.linux; 39 | }; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /haskell/default.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, lib, ghc, base, bytestring, hoogle, containers, stm, text, html-entities }: 2 | 3 | mkDerivation rec { 4 | pname = "rofi-hoogle-hs"; 5 | version = "0.1.0.0"; 6 | src = ./.; 7 | libraryHaskellDepends = [ base bytestring hoogle containers stm text html-entities]; 8 | license = lib.licenses.bsd3; 9 | postInstall = '' 10 | cp -av $out/lib/ghc-${ghc.version}/lib/* $out/lib; 11 | mkdir $out/include 12 | cp $src/csrc/rofi_hoogle_hs.h $out/include 13 | mkdir $out/lib/pkgconfig 14 | 15 | cat < $out/lib/pkgconfig/rofiHoogleNative.pc 16 | prefix=$out 17 | exec_prefix=$${prefix} 18 | libdir=$${prefix}/lib 19 | includedir=$${prefix}/include 20 | 21 | Name: ${pname} 22 | Description: search hoogle 23 | Version: 0.1.0 24 | Cflags: -I$out/include 25 | Libs: -L$out/lib -lrofi-hoogle-native 26 | END 27 | ''; 28 | } 29 | -------------------------------------------------------------------------------- /rofi-hoogle-plugin/src/Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_ROOT ?= $(shell pkg-config --variable=pluginsdir rofi) 2 | 3 | CC=gcc 4 | 5 | LIBS= glib-2.0 \ 6 | rofi \ 7 | cairo \ 8 | rofiHoogleNative \ 9 | 10 | LIB_CFLAGS= $(shell for lib in ${LIBS}; do pkg-config --cflags $${lib}; done) 11 | LIB_LFLAGS= $(shell for lib in ${LIBS}; do pkg-config --libs $${lib}; done) 12 | 13 | CFLAGS= -std=gnu11 \ 14 | -g \ 15 | -Os \ 16 | -Wall \ 17 | -Wextra \ 18 | -Wno-unused \ 19 | -Werror \ 20 | ${LIB_CFLAGS} 21 | 22 | LFLAGS= ${LIB_LFLAGS} 23 | 24 | CCLS_FLAGS= 25 | 26 | .PHONY: all 27 | all: rofi-hoogle.so 28 | 29 | %.o: %.c 30 | $(CC) $(CFLAGS) -fPIC -c $(<) -o $(@) 31 | 32 | rofi-hoogle.so: plugin.o 33 | $(CC) $(LFLAGS) -shared -o $(@) $(<) 34 | 35 | .PHONY: clean 36 | clean: 37 | -rm -f plugin.so 38 | -rm -f *.o 39 | 40 | .PHONY: install 41 | install: rofi-hoogle.so 42 | cp rofi-hoogle.so ${INSTALL_ROOT}/lib/rofi/rofi-hoogle.so 43 | 44 | .PHONY: .ccls 45 | .ccls: 46 | @echo "clang" 47 | @for flag in $(CFLAGS) ; do \ 48 | echo $$flag; \ 49 | done 50 | -------------------------------------------------------------------------------- /haskell/csrc/rofi_hoogle_hs.h: -------------------------------------------------------------------------------- 1 | #ifndef __ROFI_HOOGLE_HS_H__ 2 | #define __ROFI_HOOGLE_HS_H__ 3 | 4 | void hs_hoogle_search_init(void); 5 | void hs_hoogle_search_end(void); 6 | char* search_hoogle(const char *search_string); 7 | 8 | struct hoogle_secondary_result { 9 | char *secondary_result_url; 10 | char *secondary_result_package; // may be NULL 11 | char *secondary_result_module; // may be NULL 12 | struct hoogle_secondary_result *next; 13 | }; 14 | 15 | typedef struct hoogle_secondary_result hoogle_secondary_result_t; 16 | 17 | struct hoogle_search_result { 18 | char *search_result_name; 19 | char *search_result_primary_url; 20 | char *search_result_primary_package; // may be NULL 21 | char *search_result_primary_module; // may be NULL 22 | int secondary_search_result_count; 23 | hoogle_secondary_result_t *secondary_results; 24 | }; 25 | 26 | typedef struct hoogle_search_result hoogle_search_result_t; 27 | 28 | struct hoogle_result_set { 29 | hoogle_search_result_t search_result; 30 | struct hoogle_result_set *next; 31 | }; 32 | 33 | typedef struct hoogle_result_set hoogle_result_set_t; 34 | 35 | struct hoogle_search_state { 36 | hoogle_result_set_t *results; 37 | unsigned int result_count; 38 | }; 39 | 40 | typedef struct hoogle_search_state hoogle_search_state_t; 41 | 42 | hoogle_search_state_t* hs_preprocess_input(const char* input); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /haskell/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Rebecca Skinner 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Rebecca Skinner nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1711703276, 24 | "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /haskell/rofi-hoogle.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: rofi-hoogle-hs 3 | version: 0.1.0.0 4 | synopsis: Query Hoogle from Rofi 5 | 6 | -- A longer description of the package. 7 | -- description: 8 | homepage: github.com/rebeccaskinner/rofi-hoogle 9 | 10 | -- A URL where users can report bugs. 11 | -- bug-reports: 12 | license: BSD-3-Clause 13 | license-file: LICENSE 14 | author: Rebecca Skinner 15 | maintainer: rebecca@rebeccaskinner.net 16 | 17 | -- A copyright notice. 18 | -- copyright: 19 | category: Development 20 | extra-source-files: CHANGELOG.md 21 | 22 | library 23 | build-depends: base 24 | , hoogle 25 | , bytestring 26 | , containers 27 | , unordered-containers 28 | , stm 29 | , text 30 | , html-entities 31 | exposed-modules: HoogleQuery 32 | , HoogleQuery.SearchHoogle 33 | , HoogleQuery.ResultSorting 34 | , PangoUtils 35 | 36 | hs-source-dirs: src 37 | 38 | foreign-library rofi-hoogle-native 39 | type: native-shared 40 | lib-version-info: 0:0:1 41 | 42 | other-modules: HoogleQuery.Native 43 | , HoogleQuery.SearchHoogle 44 | , HoogleQuery.ResultSorting 45 | , PangoUtils 46 | 47 | -- Modules included in this executable, other than Main. 48 | -- other-modules: 49 | 50 | -- LANGUAGE extensions used by modules in this package. 51 | -- other-extensions: 52 | build-depends: base 53 | , rofi-hoogle-hs 54 | , hoogle 55 | , bytestring 56 | , containers 57 | , unordered-containers 58 | , stm 59 | , text 60 | , html-entities 61 | hs-source-dirs: src 62 | c-sources: csrc/wrapper.c 63 | default-language: Haskell2010 64 | -------------------------------------------------------------------------------- /haskell/src/HoogleQuery/SearchHoogle.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | {-# LANGUAGE ImportQualifiedPost #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module HoogleQuery.SearchHoogle where 6 | 7 | import Hoogle 8 | import Data.Set qualified as Set 9 | import Control.Monad 10 | import Control.Concurrent.STM.TBQueue 11 | import Control.Concurrent 12 | import Control.Monad.STM 13 | import Control.Exception 14 | import Data.IORef 15 | import Data.List 16 | import Debug.Trace 17 | 18 | hoogleLookup :: String -> IO [Target] 19 | hoogleLookup query = do 20 | db <- defaultDatabaseLocation 21 | withDatabase db (pure . (`searchDatabase` query)) 22 | 23 | searchHoogle :: String -> IO String 24 | searchHoogle input = do 25 | results <- hoogleLookup input 26 | pure . unlines . map (show . targetItem) $ results 27 | 28 | spawnSearchWorker :: IO (String -> IO String) 29 | spawnSearchWorker = do 30 | dbLoc <- defaultDatabaseLocation 31 | db <- withDatabase dbLoc newIORef 32 | queryQueue <- newTBQueueIO 1 33 | resultsQueue <- newTBQueueIO 1 34 | _thread <- forkIO . forever $ do 35 | query <- atomically $ readTBQueue queryQueue 36 | results <- withDatabase dbLoc $ \hoogleDB -> do 37 | pure $ searchDatabase hoogleDB query 38 | atomically $ writeTBQueue resultsQueue (show results) 39 | pure $ \query -> do 40 | atomically $ writeTBQueue queryQueue query 41 | results <- atomically $ readTBQueue resultsQueue 42 | putStrLn $ query <> " => " <> results 43 | pure results 44 | 45 | shouldSearchString :: String -> Bool 46 | shouldSearchString input = 47 | let 48 | (parenBalance, bracketBalance, trailingSpaces, totalLen) = foldl' (flip updateCharState) (0,0,0,0) input 49 | in (parenBalance == 0 && bracketBalance == 0) && ((trailingSpaces >= 2) || (totalLen >= 15)) 50 | where 51 | updateCharState :: Char -> (Int,Int,Int,Int) -> (Int,Int,Int,Int) 52 | updateCharState c st@(parens, brackets, trailingSpaces, totalLen) = 53 | case c of 54 | '(' -> (parens + 1, brackets, 0, totalLen + 1) 55 | ')' -> (parens - 1, brackets, 0, totalLen + 1) 56 | '[' -> (parens, brackets + 1, 0, totalLen + 1) 57 | ']' -> (parens, brackets - 1, 0, totalLen + 1) 58 | ' ' -> (parens, brackets, trailingSpaces + 1, totalLen + 1) 59 | _otherwise -> (parens, brackets, 0, totalLen + 1) 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Hoogle Plugin for Rofi 2 | 3 | rofi-hoogle is a plugin for [the rofi application 4 | launcher](https://github.com/davatorium/rofi) that let's you search Hoogle and 5 | open the results in your browser. This software is in its early stages right 6 | now, but should be usable. 7 | 8 | ## Usage 9 | 10 | Launch rofi with: `rofi -modi hoogle -show hoogle` and type in your query. 11 | 12 | - rofi-hoogle will not try to auto-complete any searches with unbalanced 13 | parentheses or brackets. This improves performance by not trying to search 14 | too often. 15 | - By default, auto-searching will only start after you've entered at least 15 16 | characters. If you want to search something with fewer characters, enter two 17 | spaces at the end of your query to immediately auto-complete. 18 | 19 | ## Installation 20 | 21 | ### On NixOS 22 | 23 | On NixOS you can make an overlay overriding the `plugins` argument to the `rofi` package. 24 | 25 | ```nix 26 | { config, pkgs, ... }: 27 | 28 | { 29 | environment.systemPackages = [ 30 | rofiWithHoogle 31 | ... 32 | ]; 33 | 34 | nixpkgs.overlays = [ 35 | (self: super: { 36 | rofiWithHoogle = let 37 | rofi-hoogle-src = pkgs.fetchFromGitHub { 38 | owner = "rebeccaskinner"; 39 | repo = "rofi-hoogle"; 40 | rev = "27c273ff67add68578052a13f560a08c12fa5767"; 41 | sha256 = "09vx9bc8s53c575haalcqkdwy44ys1j8v9k2aaly7lndr19spp8f"; 42 | }; 43 | rofi-hoogle = import "${rofi-hoogle-src}/release.nix"; 44 | in super.rofi.override { plugins = [ rofi-hoogle.rofi-hoogle ]; }; 45 | }) 46 | ... 47 | ]; 48 | } 49 | ``` 50 | 51 | ### With Home-Manager 52 | 53 | If you are using [home manager](https://github.com/nix-community/home-manager) 54 | to manage your desktop environment, you can import this package and add it as a 55 | plugin: 56 | 57 | ```nix 58 | { pkgs, ... }: 59 | let 60 | rofi-hoogle-src = pkgs.fetchFromGitHub { 61 | owner = "rebeccaskinner"; 62 | repo = "rofi-hoogle"; 63 | rev = "27c273ff67add68578052a13f560a08c12fa5767"; 64 | sha256 = "09vx9bc8s53c575haalcqkdwy44ys1j8v9k2aaly7lndr19spp8f"; 65 | }; 66 | rofi-hoogle = import "${rofi-hoogle-src}/release.nix"; 67 | in 68 | { 69 | programs.rofi = { 70 | enable = true; 71 | terminal = "${pkgs.kitty}/bin/kitty"; 72 | theme = ./themes/darkplum.rasi; 73 | plugins = with pkgs; [ 74 | rofi-emoji 75 | rofi-calc 76 | rofi-hoogle.rofi-hoogle 77 | ]; 78 | }; 79 | } 80 | ``` 81 | 82 | ### Manually From Source 83 | 84 | First, [install nix](https://nixos.org/download.html), then run: 85 | 86 | ``` 87 | nix-build release.nix 88 | ``` 89 | 90 | Finally, copy the plugin into your rofi plugin directory: 91 | 92 | ``` 93 | cp result/lib/rofi/rofi-hoogle.so $(pkg-config --variable=pluginsdir rofi) 94 | ``` 95 | -------------------------------------------------------------------------------- /haskell/src/HoogleQuery/ResultSorting.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DerivingStrategies #-} 2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 3 | {-# LANGUAGE ImportQualifiedPost #-} 4 | module HoogleQuery.ResultSorting where 5 | import Hoogle 6 | import Data.Maybe 7 | import Data.List 8 | import Data.Ord 9 | import Debug.Trace 10 | import qualified Data.HashMap.Strict as HashMap 11 | 12 | data PackageType 13 | = PackageTypeBase 14 | | PackageTypeCoreLibrary 15 | | PackageTypeGHCLibrary 16 | | PackageTypePopularLibrary 17 | | PackageTypeOtherLibrary 18 | deriving (Eq, Ord, Enum, Show) 19 | 20 | newtype PackageClassification = PackageClassification 21 | { getClassifications :: HashMap.HashMap String PackageType } 22 | deriving newtype (Semigroup, Monoid) 23 | 24 | defaultPackageClassification :: PackageClassification 25 | defaultPackageClassification = 26 | PackageClassification . HashMap.fromList $ 27 | [ ("base", PackageTypeBase) 28 | -- core libraries 29 | , ("array", PackageTypeCoreLibrary) 30 | , ("deepseq", PackageTypeCoreLibrary) 31 | , ("directory", PackageTypeCoreLibrary) 32 | , ("filepath", PackageTypeCoreLibrary) 33 | , ("mtl", PackageTypeCoreLibrary) 34 | , ("primitive", PackageTypeCoreLibrary) 35 | , ("process", PackageTypeCoreLibrary) 36 | , ("random", PackageTypeCoreLibrary) 37 | , ("stm", PackageTypeCoreLibrary) 38 | , ("template-haskell", PackageTypeCoreLibrary) 39 | , ("unix", PackageTypeCoreLibrary) 40 | , ("vector", PackageTypeCoreLibrary) 41 | , ("Win32", PackageTypeCoreLibrary) 42 | -- Non-Core libraries that ship with GHC 43 | , ("containers", PackageTypeGHCLibrary) 44 | , ("hoopl", PackageTypeGHCLibrary) 45 | , ("parallel", PackageTypeGHCLibrary) 46 | , ("pretty", PackageTypeGHCLibrary) 47 | , ("time", PackageTypeGHCLibrary) 48 | , ("xhtml", PackageTypeGHCLibrary) 49 | , ("ghc-prim", PackageTypeGHCLibrary) 50 | , ("hpc", PackageTypeGHCLibrary) 51 | -- Popular libraries that should be prioritized in search results, 52 | -- not based on any particular strong evidence, but with a slight 53 | -- bias toward "low-level" things, things with a lot of operators, 54 | -- or things I happen to be using lately. Not necessarily an 55 | -- endorsement. 56 | , ("aeson", PackageTypePopularLibrary) 57 | , ("bytestring", PackageTypePopularLibrary) 58 | , ("text", PackageTypePopularLibrary) 59 | , ("network", PackageTypePopularLibrary) 60 | , ("attoparsec", PackageTypePopularLibrary) 61 | , ("megaparsec", PackageTypePopularLibrary) 62 | , ("rio", PackageTypePopularLibrary) 63 | , ("relude", PackageTypePopularLibrary) 64 | , ("mono-traversable", PackageTypePopularLibrary) 65 | , ("warp", PackageTypePopularLibrary) 66 | , ("servant", PackageTypePopularLibrary) 67 | , ("pandoc", PackageTypePopularLibrary) 68 | , ("random", PackageTypePopularLibrary) 69 | , ("lens", PackageTypePopularLibrary) 70 | , ("cryptonite", PackageTypePopularLibrary) 71 | , ("HTTP", PackageTypePopularLibrary) 72 | , ("optparse-applicative", PackageTypePopularLibrary) 73 | , ("transformers", PackageTypePopularLibrary) 74 | , ("http-types", PackageTypePopularLibrary) 75 | , ("foundation", PackageTypePopularLibrary) 76 | , ("wai", PackageTypePopularLibrary) 77 | , ("parsec", PackageTypePopularLibrary) 78 | , ("parallel", PackageTypePopularLibrary) 79 | , ("persistent", PackageTypePopularLibrary) 80 | , ("esqueleto", PackageTypePopularLibrary) 81 | , ("unliftio", PackageTypePopularLibrary) 82 | , ("unliftio-core", PackageTypePopularLibrary) 83 | ] 84 | 85 | classifyPackage :: PackageClassification -> String -> PackageType 86 | classifyPackage (PackageClassification classifications) pkgName = 87 | fromMaybe PackageTypeOtherLibrary $ HashMap.lookup pkgName classifications 88 | 89 | sortTargetsByClassification :: [Target] -> [Target] 90 | sortTargetsByClassification = 91 | let 92 | classification :: Target -> PackageType 93 | classification p = 94 | let 95 | pName = maybe "" fst (targetPackage p) 96 | c = classifyPackage defaultPackageClassification pName 97 | in trace ("classification for " <> pName <> " is " <> show c) $ c 98 | in 99 | sortOn classification 100 | 101 | sortTargets :: [Target] -> [[Target]] 102 | sortTargets = 103 | sortOn classifyTargetSet 104 | . HashMap.elems 105 | . HashMap.map sortTargetsByClassification 106 | . foldr insertTarget HashMap.empty 107 | where 108 | insertTarget :: Target -> HashMap.HashMap String [Target] -> HashMap.HashMap String [Target] 109 | insertTarget target accumulatorMap = 110 | let key = maybe "" fst $ targetPackage target 111 | in HashMap.insertWith (<>) key [target] accumulatorMap 112 | 113 | classifyTargetSet :: [Target] -> PackageType 114 | classifyTargetSet [] = PackageTypeOtherLibrary 115 | classifyTargetSet(t:_) = 116 | let n = maybe "" fst (targetPackage t) 117 | in classifyPackage defaultPackageClassification n 118 | -------------------------------------------------------------------------------- /rofi-hoogle-plugin/src/plugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define STR_OR(s,def) ((NULL == s) ? (def) : (s)) 8 | 9 | /* glib */ 10 | #include 11 | 12 | /* rofi */ 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | G_MODULE_EXPORT Mode mode; 20 | 21 | #define UNUSED __attribute__((unused)) 22 | 23 | static int hs_runtime_is_started = 0; 24 | 25 | static hoogle_result_set_t* get_nth_result(hoogle_search_state_t *state, unsigned int offset) { 26 | hoogle_result_set_t *result = NULL; 27 | if (NULL == state) {return NULL;} 28 | if (offset >= state->result_count) { return NULL; } 29 | result = state->results; 30 | for (; offset > 0; offset--) { 31 | result = result->next; 32 | } 33 | return result; 34 | } 35 | 36 | static void start_hs_runtime() { 37 | if (hs_runtime_is_started) { 38 | return; 39 | } 40 | hs_hoogle_search_init(); 41 | hs_runtime_is_started = 1; 42 | } 43 | 44 | static void stop_hs_runtime() { 45 | if (!hs_runtime_is_started) { 46 | return; 47 | } 48 | hs_hoogle_search_end(); 49 | hs_runtime_is_started = 0; 50 | } 51 | 52 | typedef struct { 53 | char **array; 54 | unsigned int array_length; 55 | } hoogle_mode_private_data; 56 | 57 | static void get_hoogle_plugin(UNUSED Mode *sw) {} 58 | 59 | static int hoogle_plugin_mode_init(UNUSED Mode *sw) { 60 | start_hs_runtime(); 61 | if (NULL == mode_get_private_data(sw)) { 62 | get_hoogle_plugin(sw); 63 | } 64 | return TRUE; 65 | } 66 | 67 | static unsigned int hoogle_plugin_get_num_entries(const Mode *sw) { 68 | printf("checking number of entries...\n"); 69 | hoogle_search_state_t *private_data = mode_get_private_data(sw); 70 | if (NULL == private_data) { 71 | return 0; 72 | } 73 | return private_data->result_count; 74 | } 75 | 76 | static ModeMode hoogle_plugin_mode_result( 77 | Mode *sw, 78 | int mretv, 79 | UNUSED char **input, 80 | unsigned int selected_line 81 | ) { 82 | printf("hoogle_plugin_mode_result\n"); 83 | ModeMode retv = MODE_EXIT; 84 | hoogle_search_state_t *private_data = mode_get_private_data(sw); 85 | 86 | 87 | if ( mretv & MENU_NEXT ) {retv = NEXT_DIALOG;} 88 | else if ( mretv & MENU_PREVIOUS ) {retv = PREVIOUS_DIALOG;} 89 | else if ( mretv & MENU_QUICK_SWITCH ) {retv = ( mretv & MENU_LOWER_MASK );} 90 | else if ( mretv & MENU_OK ) { 91 | hoogle_result_set_t *result = get_nth_result(private_data, selected_line); 92 | if (NULL == result) { 93 | return MODE_EXIT; 94 | } 95 | 96 | char* command = g_strdup_printf("xdg-open %s", result->search_result.search_result_primary_url); 97 | g_spawn_command_line_sync(command, NULL, NULL, NULL, NULL); 98 | retv = MODE_EXIT; 99 | } 100 | else if ( (mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE ) {retv = RELOAD_DIALOG;} 101 | return retv; 102 | } 103 | 104 | static void hoogle_plugin_mode_destroy(UNUSED Mode *sw) { 105 | stop_hs_runtime(); 106 | } 107 | 108 | void debug_hoogle_search_state(const hoogle_search_state_t *state) { 109 | if (NULL == state) { 110 | printf("search state is NULL\n"); 111 | return; 112 | } 113 | printf("%d search results\n", state->result_count); 114 | for (hoogle_result_set_t *result = state->results; result != NULL; result = result->next) { 115 | printf("result info for: %s\n", result->search_result.search_result_name); 116 | printf("url: %s\n", result->search_result.search_result_primary_url); 117 | printf("package: %s\n", STR_OR(result->search_result.search_result_primary_package, "no package")); 118 | printf("module: %s\n\n", STR_OR(result->search_result.search_result_primary_module, "no module")); 119 | printf("%d secondary matches\n", result->search_result.secondary_search_result_count); 120 | printf("\n\n"); 121 | } 122 | } 123 | 124 | extern void rofi_view_reload(void); 125 | 126 | static void swap_result_buffer(const void *old_state) { 127 | static const void *last_known_state = NULL; 128 | if (old_state != last_known_state) { 129 | printf("flipping buffer"); 130 | last_known_state = old_state; 131 | rofi_view_reload(); 132 | } 133 | } 134 | 135 | static char *hoogle_plugin_preprocess_input(Mode *sw, const char *input) { 136 | hoogle_search_state_t *result_state = hs_preprocess_input(input); 137 | if (NULL != result_state) { 138 | mode_set_private_data(sw, NULL); 139 | mode_set_private_data(sw, result_state); 140 | } 141 | swap_result_buffer(result_state); 142 | return g_strdup(input); 143 | } 144 | 145 | static char *hoogle_plugin_get_display_value( 146 | const Mode *sw, 147 | unsigned int selected_line, 148 | UNUSED int *state, 149 | UNUSED GList **attr_list, 150 | int get_entry 151 | ) { 152 | 153 | *state |= 8; 154 | if (!get_entry) { return NULL; } 155 | hoogle_search_state_t *private_data = mode_get_private_data(sw); 156 | if (NULL == private_data) { 157 | printf("calling hoogle_plugin_get_display_value...no private data, exiting...\n"); 158 | return g_strdup("n/a"); 159 | } 160 | hoogle_result_set_t *result = get_nth_result(private_data, selected_line); 161 | if (NULL == result) { 162 | printf("cannot get %d result", selected_line); 163 | return g_strdup("n/a"); 164 | } 165 | return( 166 | g_markup_printf_escaped( 167 | "%s\r%s %s", 168 | result->search_result.search_result_name, 169 | STR_OR(result->search_result.search_result_primary_package, ""), 170 | STR_OR(result->search_result.search_result_primary_module,"") 171 | ) 172 | ); 173 | } 174 | 175 | static int hoogle_plugin_token_match( 176 | UNUSED const Mode *sw, 177 | UNUSED rofi_int_matcher **tokens, 178 | UNUSED unsigned int index 179 | ) { 180 | return TRUE; 181 | } 182 | 183 | /* formats and displays a mardown rendering of the final result */ 184 | static char *hoogle_plugin_get_message(UNUSED const Mode *sw) { 185 | return g_strdup("search must be at least 15 characters (end with two spaces to search early)"); 186 | } 187 | 188 | Mode mode = { 189 | .abi_version = ABI_VERSION, 190 | .name = "hoogle", 191 | .cfg_name_key = "display-hoogle", 192 | ._init = hoogle_plugin_mode_init, 193 | ._get_num_entries = hoogle_plugin_get_num_entries, 194 | ._result = hoogle_plugin_mode_result, 195 | ._destroy = hoogle_plugin_mode_destroy, 196 | ._token_match = hoogle_plugin_token_match, 197 | ._get_display_value = hoogle_plugin_get_display_value, 198 | ._get_message = hoogle_plugin_get_message, 199 | ._get_completion = NULL, 200 | ._preprocess_input = hoogle_plugin_preprocess_input, 201 | .private_data = NULL, 202 | .free = NULL, 203 | }; 204 | -------------------------------------------------------------------------------- /haskell/src/HoogleQuery/Native.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ForeignFunctionInterface #-} 2 | {-# LANGUAGE ImportQualifiedPost #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE RecordWildCards #-} 5 | {-# LANGUAGE TypeApplications #-} 6 | 7 | 8 | module HoogleQuery.Native where 9 | import PangoUtils 10 | import HoogleQuery.ResultSorting 11 | import HoogleQuery.SearchHoogle 12 | import Data.Text.Lazy qualified as LazyText 13 | 14 | import Control.Monad 15 | import qualified Data.ByteString as BS 16 | import Data.Foldable 17 | import Data.IORef 18 | import Data.List 19 | import Data.List.NonEmpty qualified as NonEmpty 20 | import Data.Maybe 21 | import Data.Ord (comparing) 22 | import Foreign.C 23 | import Foreign.C.String 24 | import Foreign.C.Types 25 | import Foreign.ForeignPtr 26 | import Foreign.Marshal.Alloc 27 | import Foreign.Ptr 28 | import Foreign.Storable 29 | import Hoogle 30 | import System.IO.Unsafe (unsafePerformIO) 31 | import qualified GHC.Pack as LazyText 32 | 33 | data HoogleSecondaryResult = HoogleSecondaryResult 34 | { secondaryResultURL :: CString 35 | , secondaryResultPackage :: CString 36 | , secondaryResultModule :: CString 37 | , secondaryResultNext :: Ptr HoogleSecondaryResult 38 | } 39 | 40 | instance Storable HoogleSecondaryResult where 41 | sizeOf _ = (3 * sizeOf @CString undefined) 42 | + sizeOf @(Ptr HoogleSecondaryResult) undefined 43 | alignment _ = max (alignment @CString undefined) 44 | (alignment @(Ptr HoogleSecondaryResult) undefined) 45 | peek inPtr = HoogleSecondaryResult 46 | <$> peek (castPtr inPtr) 47 | <*> peekElemOff (castPtr inPtr) 1 48 | <*> peekElemOff (castPtr inPtr) 2 49 | <*> peekByteOff inPtr (3 * sizeOf @CString undefined) 50 | 51 | poke outPtr HoogleSecondaryResult{..} = do 52 | poke (castPtr outPtr) secondaryResultURL 53 | pokeElemOff (castPtr outPtr) 1 secondaryResultPackage 54 | pokeElemOff (castPtr outPtr) 2 secondaryResultModule 55 | pokeByteOff outPtr (3 * sizeOf @CString undefined) secondaryResultNext 56 | 57 | freeHoogleSecondaryResult :: Ptr HoogleSecondaryResult -> IO () 58 | freeHoogleSecondaryResult p 59 | | p == nullPtr = pure () 60 | | otherwise = do 61 | HoogleSecondaryResult{..} <- peek p 62 | free secondaryResultURL 63 | when (secondaryResultPackage /= nullPtr) $ 64 | free secondaryResultPackage 65 | when (secondaryResultModule /= nullPtr) $ 66 | free secondaryResultModule 67 | freeHoogleSecondaryResult secondaryResultNext 68 | free p 69 | 70 | data HoogleSearchResult = HoogleSearchResult 71 | { -- | The actual (html) name of the result 72 | searchResultName :: CString 73 | , -- | The URL of the primary location of this result 74 | searchResultPrimaryURL :: CString 75 | , -- | The package name for the primary result; may be null 76 | searchResultPrimaryPackage :: CString 77 | , -- | The module name for the primary result; may be null 78 | searchResultPrimaryModule :: CString 79 | , -- | The number of additional results that we've found 80 | searchResultSecondaryResultCount :: Int 81 | , -- | The secondary results, if any (nullPtr if 'searchResultSecondaryResultCount' is 0) 82 | searchResultSecondaryResults :: Ptr HoogleSecondaryResult 83 | } 84 | 85 | instance Storable HoogleSearchResult where 86 | sizeOf _ = (4 * sizeOf @CString undefined) 87 | + sizeOf @Int undefined 88 | + sizeOf @(Ptr HoogleSecondaryResult) undefined 89 | alignment _ = maximum [ alignment @CString undefined 90 | , alignment @Int undefined 91 | , alignment @(Ptr HoogleSecondaryResult) undefined 92 | ] 93 | peek inPtr = HoogleSearchResult 94 | <$> peek (castPtr inPtr) 95 | <*> peekElemOff (castPtr inPtr) 1 96 | <*> peekElemOff (castPtr inPtr) 2 97 | <*> peekElemOff (castPtr inPtr) 3 98 | <*> peekByteOff inPtr (4 * sizeOf @CString undefined) 99 | <*> peekByteOff inPtr ((4 * sizeOf @CString undefined) + sizeOf @Int undefined) 100 | 101 | poke outPtr HoogleSearchResult{..} = do 102 | poke (castPtr outPtr) searchResultName 103 | pokeElemOff (castPtr outPtr) 1 searchResultPrimaryURL 104 | pokeElemOff (castPtr outPtr) 2 searchResultPrimaryPackage 105 | pokeElemOff (castPtr outPtr) 3 searchResultPrimaryModule 106 | pokeByteOff outPtr (4 * sizeOf @CString undefined) searchResultSecondaryResultCount 107 | pokeByteOff outPtr ((4 * sizeOf @CString undefined) + sizeOf @Int undefined) searchResultSecondaryResults 108 | 109 | freeHoogleSearchResult :: HoogleSearchResult -> IO () 110 | freeHoogleSearchResult HoogleSearchResult{..} = do 111 | free searchResultName 112 | free searchResultPrimaryURL 113 | when (searchResultPrimaryPackage /= nullPtr) $ 114 | free searchResultPrimaryPackage 115 | when (searchResultPrimaryModule /= nullPtr) $ 116 | free searchResultPrimaryModule 117 | freeHoogleSecondaryResult searchResultSecondaryResults 118 | 119 | freeHoogleSearchResultPtr :: Ptr HoogleSearchResult -> IO () 120 | freeHoogleSearchResultPtr p = 121 | if p == nullPtr 122 | then pure () 123 | else peek p >>= freeHoogleSearchResult >> free p 124 | 125 | maybeCString :: Maybe String -> IO CString 126 | maybeCString Nothing = pure nullPtr 127 | maybeCString (Just s) = newCString s 128 | 129 | hoogleSearchResultFromList :: NonEmpty.NonEmpty Target -> IO HoogleSearchResult 130 | hoogleSearchResultFromList (primary NonEmpty.:| rest) = HoogleSearchResult 131 | <$> newCString (cleanupHTML $ targetItem primary) 132 | <*> newCString (targetURL primary) 133 | <*> maybeCString (fst <$> targetPackage primary) 134 | <*> maybeCString (fst <$> targetModule primary) 135 | <*> pure (length rest) 136 | <*> secondaryResultsFromList rest 137 | where 138 | secondaryResultsFromList [] = pure nullPtr 139 | secondaryResultsFromList (result:results) = do 140 | p <- malloc 141 | secondaryResult <- HoogleSecondaryResult 142 | <$> newCString (targetURL result) 143 | <*> maybeCString (fst <$> targetPackage result) 144 | <*> maybeCString (fst <$> targetModule result) 145 | <*> secondaryResultsFromList results 146 | poke p secondaryResult 147 | pure p 148 | 149 | hoogleSearchResultFromListPtr :: [Target] -> IO (Ptr HoogleSearchResult) 150 | hoogleSearchResultFromListPtr [] = pure nullPtr 151 | hoogleSearchResultFromListPtr (primary:rest) = do 152 | result <- malloc 153 | searchResult <- hoogleSearchResultFromList (primary NonEmpty.:| rest) 154 | poke result searchResult 155 | pure result 156 | 157 | data HoogleResultSet = HoogleResultSet 158 | { hoogleResultValue :: HoogleSearchResult 159 | , hoogleResultNext :: Ptr HoogleResultSet 160 | } 161 | 162 | instance Storable HoogleResultSet where 163 | sizeOf _ = (5 * sizeOf @CString undefined) + sizeOf @(Ptr HoogleResultSet) undefined 164 | alignment _ = max (alignment @CString undefined) (alignment @(Ptr HoogleResultSet) undefined) 165 | peek resultPtr = HoogleResultSet 166 | <$> peek (castPtr resultPtr) 167 | <*> peekByteOff resultPtr (sizeOf @HoogleSearchResult undefined) 168 | 169 | poke outPtr HoogleResultSet{..} = do 170 | poke (castPtr outPtr) hoogleResultValue 171 | pokeByteOff outPtr (sizeOf @HoogleSearchResult undefined) hoogleResultNext 172 | 173 | freeHoogleResultSet :: Ptr HoogleResultSet -> IO () 174 | freeHoogleResultSet resultPtr 175 | | resultPtr == nullPtr = pure () 176 | | otherwise = do 177 | HoogleResultSet{..} <- peek resultPtr 178 | freeHoogleSearchResult hoogleResultValue 179 | freeHoogleResultSet hoogleResultNext 180 | free resultPtr 181 | pure () 182 | 183 | hoogleSearchResultSetFromTargetGroups :: [[Target]] -> IO (Ptr HoogleResultSet) 184 | hoogleSearchResultSetFromTargetGroups [] = pure nullPtr 185 | hoogleSearchResultSetFromTargetGroups (target:targets) = 186 | case target of 187 | [] -> hoogleSearchResultSetFromTargetGroups targets 188 | (t:ts) -> do 189 | p <- malloc 190 | poke p =<< HoogleResultSet 191 | <$> hoogleSearchResultFromList (t NonEmpty.:| ts) 192 | <*> hoogleSearchResultSetFromTargetGroups targets 193 | pure p 194 | 195 | data HoogleSearchState = HoogleSearchState 196 | { hoogleStateResults :: Ptr HoogleResultSet 197 | , hoogleStateResultCount :: Int 198 | } 199 | 200 | instance Storable HoogleSearchState where 201 | sizeOf _ = sizeOf @(Ptr HoogleResultSet) undefined + sizeOf @Int undefined 202 | alignment _ = max (alignment @(Ptr HoogleResultSet) undefined) (alignment @Int undefined) 203 | peek inPtr = HoogleSearchState 204 | <$> peek (castPtr inPtr) 205 | <*> peekByteOff inPtr (sizeOf @(Ptr HoogleResultSet) undefined) 206 | poke outPtr HoogleSearchState{..} = do 207 | poke (castPtr outPtr) hoogleStateResults 208 | pokeByteOff outPtr (sizeOf @(Ptr HoogleResultSet) undefined) hoogleStateResultCount 209 | 210 | freeHoogleSearchState :: Ptr HoogleSearchState -> IO () 211 | freeHoogleSearchState p 212 | | p == nullPtr = pure () 213 | | otherwise = do 214 | HoogleSearchState{..} <- peek p 215 | freeHoogleResultSet hoogleStateResults 216 | free p 217 | 218 | hoogleSearchStateFromList :: [Target] -> IO (Ptr HoogleSearchState) 219 | hoogleSearchStateFromList [] = pure nullPtr 220 | hoogleSearchStateFromList targets = do 221 | let 222 | targetGroups = sortTargets targets 223 | resultCount = length targetGroups 224 | p <- malloc 225 | resultSet <- hoogleSearchResultSetFromTargetGroups targetGroups 226 | poke p $ HoogleSearchState resultSet resultCount 227 | pure p 228 | 229 | lastResults :: IORef (Ptr HoogleSearchState) 230 | lastResults = unsafePerformIO $ newIORef nullPtr 231 | {-# NOINLINE lastResults #-} 232 | 233 | lastQuery :: IORef String 234 | lastQuery = unsafePerformIO $ newIORef "" 235 | {-# NOINLINE lastQuery #-} 236 | 237 | updateResults :: Ptr HoogleSearchState -> IO () 238 | updateResults newResults = do 239 | oldResults <- readIORef lastResults 240 | when (oldResults /= nullPtr) $ 241 | freeHoogleSearchState oldResults 242 | writeIORef lastResults newResults 243 | 244 | updateResults' :: Ptr HoogleSearchState -> IO (Ptr HoogleSearchState) 245 | updateResults' p = updateResults p >> pure p 246 | 247 | foreign export ccall "search_hoogle" searchHoogleNative :: CString -> IO CString 248 | 249 | searchHoogleNative :: CString -> IO CString 250 | searchHoogleNative input = 251 | peekCString input >>= searchHoogle >>= newCString 252 | 253 | foreign export ccall "hs_preprocess_input" preprocessInput :: CString -> IO (Ptr HoogleSearchState) 254 | 255 | preprocessInput :: CString -> IO (Ptr HoogleSearchState) 256 | preprocessInput input = do 257 | dbName <- defaultDatabaseLocation 258 | input' <- peekCString input 259 | lastQueryInput <- readIORef lastQuery 260 | if input' == lastQueryInput 261 | then readIORef lastResults 262 | else (do 263 | writeIORef lastQuery input' 264 | if shouldSearchString input' 265 | then withDatabase dbName (searchUpdateResults input') 266 | else pure nullPtr) 267 | 268 | searchUpdateResults :: String -> Database -> IO (Ptr HoogleSearchState) 269 | searchUpdateResults query db = updateSearchResults $ searchDatabase db query 270 | 271 | updateSearchResults :: [Target] -> IO (Ptr HoogleSearchState) 272 | updateSearchResults targets = 273 | hoogleSearchStateFromList targets >>= updateResults' 274 | --------------------------------------------------------------------------------