From 55dc09017e672ec9705fd11746f48d1de98b443f Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:07:44 -0400 Subject: [PATCH] Compiler Picker UI Improvements (#4849) Alrighty, round 2! This PR makes the compiler picker dropdown taller (max height is to the bottom of the screen) ![image](https://user-images.githubusercontent.com/51220084/224517409-17706ba6-fe85-444a-ad3b-f244f66eea5b.png) I've added a toolbar button for opening the popout as well as a button when the dropdown is open: ![image](https://user-images.githubusercontent.com/51220084/224517304-6478fca5-7bc1-4f87-b2ae-e9091d7d48bc.png) ![image](https://user-images.githubusercontent.com/51220084/224517317-83468805-1e9d-4bc6-8a4f-8786325f1e7b.png) Then the modal interface allows text searching, filtering by compiler category, and filtering by compiler instruction set. In the future I'd like to replace the instructionSet property with an architecture property so the filtering here can be a little better. ![image](https://user-images.githubusercontent.com/51220084/224517348-9cf40866-c5af-4274-84ba-21eb640ab2ea.png) Demo of the category filtering: ![image](https://user-images.githubusercontent.com/51220084/224517529-5370b3f9-9ad3-4340-838a-b2d0a47c7467.png) The text filtering highlights results the same as tomselect does: ![image](https://user-images.githubusercontent.com/51220084/224517490-d0dfa6d0-9b61-4be4-8cc7-ca4a4729a532.png) I moved the favorites permanently to their own column so compilers aren't jarringly shifted around the screen when favoriting / unfavoriting. Also added media queries so the modal looks ok on smaller screens: ![image](https://user-images.githubusercontent.com/51220084/224517563-bdf409c1-7e3a-4c73-812b-f02179b55799.png) ![image](https://user-images.githubusercontent.com/51220084/224517574-07fa2c8b-67d2-496d-9174-b1343e22bd62.png) I think the only other two changes from the first PR are now the active compiler is highlighted like it is in the tomselect dropdown and I figured out how to reliably focus the search bar on modal open. (Oh, and now clicking a compiler actually does something!) --------- Co-authored-by: Matt Godbolt --- etc/config/ada.amazon.properties | 3 + etc/config/assembly.amazon.properties | 6 + etc/config/c++.amazon.properties | 31 +- etc/config/c++.defaults.properties | 2 + etc/config/c.amazon.properties | 25 +- lib/common-utils.ts | 5 + lib/compiler-finder.ts | 3 +- package-lock.json | 20 +- package.json | 1 + static/.eslint-ce-static.yml | 6 +- static/highlight.ts | 81 ++++++ static/panes/compiler.ts | 6 +- static/panes/conformance-view.ts | 4 +- static/panes/executor.ts | 2 +- static/styles/explorer.scss | 133 ++++++++- static/styles/themes/dark-theme.scss | 32 +++ static/styles/themes/default-theme.scss | 30 ++ static/widgets/compiler-picker-popup.ts | 269 ++++++++++++++++++ static/widgets/compiler-picker.ts | 96 +++++-- types/compiler.interfaces.ts | 11 +- views/popups/_all.pug | 2 + views/popups/compiler-picker-modal.pug | 32 +++ views/templates/panes/compiler.pug | 4 + views/templates/panes/executor.pug | 4 + views/templates/widgets/compiler-selector.pug | 4 + 25 files changed, 764 insertions(+), 48 deletions(-) create mode 100644 static/highlight.ts create mode 100644 static/widgets/compiler-picker-popup.ts create mode 100644 views/popups/compiler-picker-modal.pug diff --git a/etc/config/ada.amazon.properties b/etc/config/ada.amazon.properties index 71c17ca0f..398fe4a19 100644 --- a/etc/config/ada.amazon.properties +++ b/etc/config/ada.amazon.properties @@ -20,6 +20,8 @@ group.gnat.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.gnat.licenseName=GNU General Public License group.gnat.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ group.gnat.supportsBinaryObject=true +group.gnat.instructionSet=amd64 +group.gnat.compilerCategories=gcc compiler.gnat82.exe=/opt/compiler-explorer/gcc-8.2.0/bin/gnatmake compiler.gnat82.semver=8.2 @@ -54,6 +56,7 @@ group.gnatcross.isSemVer=true group.gnatcross.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.gnatcross.licenseName=GNU General Public License group.gnatcross.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ +group.gnatcross.compilerCategories=gcc ################################ # GNAT for sparc diff --git a/etc/config/assembly.amazon.properties b/etc/config/assembly.amazon.properties index 79c2d534d..83ee1c470 100644 --- a/etc/config/assembly.amazon.properties +++ b/etc/config/assembly.amazon.properties @@ -12,6 +12,7 @@ group.nasm.options= group.nasm.isSemVer=true group.nasm.baseName=NASM group.nasm.compilerType=nasm +group.nasm.instructionSet=amd64 compiler.nasm21202.semver=2.12.02 compiler.nasm21202.exe=/opt/compiler-explorer/nasm-2.12.02/nasm compiler.nasm21302.semver=2.13.02 @@ -27,6 +28,7 @@ group.gnuas.versionFlag=--version group.gnuas.options=-g group.gnuas.isSemVer=true group.gnuas.baseName=x86-64 binutils +group.gnuas.instructionSet=amd64 compiler.gnuas72.exe=/opt/compiler-explorer/gcc-7.2.0/bin/as compiler.gnuas72.semver=2.27 compiler.gnuas73.exe=/opt/compiler-explorer/gcc-7.3.0/bin/as @@ -51,6 +53,7 @@ group.gnuasarm.isSemVer=true group.gnuasarm.baseName=ARM binutils group.gnuasarm.supportsExecute=false group.gnuasarm.objdumper=/opt/compiler-explorer/arm/gcc-10.2.0/arm-unknown-linux-gnueabihf/bin/arm-unknown-linux-gnueabihf-objdump +group.gnuasarm.instructionSet=arm32 compiler.gnuasarmhfg54.exe=/opt/compiler-explorer/arm/gcc-5.4.0/arm-unknown-linux-gnueabihf/bin/arm-unknown-linux-gnueabihf-as compiler.gnuasarmhfg54.name=ARMhf binutils 2.28 @@ -79,6 +82,7 @@ group.gnuasarm64.isSemVer=true group.gnuasarm64.baseName=AArch64 binutils group.gnuasarm64.supportsExecute=false group.gnuasarm64.objdumper=/opt/compiler-explorer/arm64/gcc-10.2.0/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/bin/objdump +group.gnuasarm64.instructionSet=aarch64 compiler.gnuasarm64g630.exe=/opt/compiler-explorer/arm64/gcc-6.3.0/aarch64-unknown-linux-gnueabi/bin/aarch64-unknown-linux-gnueabi-as compiler.gnuasarm64g630.name=AArch64 binutils 2.28 @@ -100,6 +104,7 @@ group.llvmas.options=-filetype=obj -o example.o group.llvmas.versionRe=LLVM version .* group.llvmas.isSemVer=true group.llvmas.baseName=x86-64 clang +group.llvmas.instructionSet=amd64 compiler.llvmas30.exe=/opt/compiler-explorer/clang+llvm-3.0-x86_64-linux-Ubuntu-11_10/bin/llvm-mc compiler.llvmas30.semver=3.0.0 compiler.llvmas31.exe=/opt/compiler-explorer/clang+llvm-3.1-x86_64-linux-ubuntu_12.04/bin/llvm-mc @@ -175,6 +180,7 @@ group.ptxas.compilerType=ptxas group.ptxas.demangler= group.ptxas.isSemVer=true group.ptxas.supportsExecute=false +group.ptxas.instructionSet=ptx compiler.ptxasnvcc91.objdumper=/opt/compiler-explorer/cuda/9.1.85/bin/nvdisasm compiler.ptxasnvcc91.semver=9.1.85 diff --git a/etc/config/c++.amazon.properties b/etc/config/c++.amazon.properties index 7d0bdc5bc..84c6281a4 100644 --- a/etc/config/c++.amazon.properties +++ b/etc/config/c++.amazon.properties @@ -29,6 +29,7 @@ group.gcc86.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.gcc86.licenseName=GNU General Public License group.gcc86.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ group.gcc86.supportsBinaryObject=true +group.gcc86.compilerCategories=gcc compiler.g412.exe=/opt/compiler-explorer/gcc-4.1.2/bin/g++ compiler.g412.semver=4.1.2 @@ -208,6 +209,7 @@ group.clang.licenseName=LLVM Apache 2 group.clang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.clang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions group.clang.supportsBinaryObject=true +group.clang.compilerCategories=clang # Ancient clangs don't support GCC toolchain option compiler.clang30.exe=/opt/compiler-explorer/clang+llvm-3.0-x86_64-linux-Ubuntu-11_10/bin/clang++ @@ -352,6 +354,7 @@ group.clangx86trunk.unwiseOptions=-march=native group.clangx86trunk.supportsPVS-Studio=true group.clangx86trunk.supportsSonar=true group.clangx86trunk.ldPath=${exePath}/../lib|${exePath}/../lib/x86_64-unknown-linux-gnu +group.clangx86trunk.compilerCategories=clang compiler.clang_trunk.exe=/opt/compiler-explorer/clang-trunk/bin/clang++ compiler.clang_trunk.semver=(trunk) @@ -421,6 +424,7 @@ group.clang-rocm.supportsSonar=true group.clang-rocm.licenseName=LLVM Apache 2 and NCSA group.clang-rocm.licenseLink=https://github.com/RadeonOpenCompute/llvm-project/blob/amd-stg-open/LICENSE.TXT # and https://github.com/RadeonOpenCompute/ROCm-Device-Libs/blob/amd-stg-open/LICENSE.TXT +group.clang-rocm.compilerCategories=clang compiler.clang-rocm-trunk.exe=/opt/compiler-explorer/clang-rocm-trunk/bin/clang++ compiler.clang-rocm-trunk.semver=(amd-stg-open) @@ -463,6 +467,7 @@ group.armclang32.objdumper=/opt/compiler-explorer/arm/gcc-10.2.0/arm-unknown-lin group.armclang32.licenseName=LLVM Apache 2 group.armclang32.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.armclang32.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.armclang32.compilerCategories=clang compiler.armv7-clang1101.exe=/opt/compiler-explorer/clang-11.0.1/bin/clang++ compiler.armv7-clang1101.semver=11.0.1 @@ -508,6 +513,7 @@ group.armclang64.instructionSet=aarch64 group.armclang64.licenseName=LLVM Apache 2 group.armclang64.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.armclang64.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.armclang64.compilerCategories=clang compiler.armv8-clang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ compiler.armv8-clang1500.semver=15.0.0 @@ -572,6 +578,7 @@ group.mosclang-trunk.compilerType=llvmmos group.mosclang-trunk.isSemVer=true group.mosclang-trunk.objdumper=/opt/compiler-explorer/llvm-mos-trunk/bin/llvm-objdump group.mosclang-trunk.objdumperType=llvm +group.mosclang-trunk.compilerCategories=clang compiler.mos-nes-cnrom-trunk.exe=/opt/compiler-explorer/llvm-mos-trunk/bin/mos-nes-cnrom-clang++ compiler.mos-nes-cnrom-trunk.semver=nes-cnrom @@ -616,6 +623,7 @@ group.rv32clang.options=-target riscv32-unknown-elf -march=rv32gc -mabi=ilp32d - group.rv32clang.objdumper=/opt/compiler-explorer/riscv32/gcc-10.2.0/riscv32-unknown-elf/bin/riscv32-unknown-elf-objdump group.rv32clang.baseName=RISC-V rv32gc clang group.rv32clang.groupName=RISC-V 32 Clang +group.rv32clang.compilerCategories=clang compiler.rv32-clang900.exe=/opt/compiler-explorer/clang-9.0.0/bin/clang++ compiler.rv32-clang900.semver=9.0.0 @@ -658,6 +666,7 @@ group.rv64clang.options=-target riscv64-unknown-linux-gnu -march=rv64gc -mabi=lp group.rv64clang.objdumper=/opt/compiler-explorer/riscv64/gcc-10.2.0/riscv64-unknown-linux-gnu/bin/riscv64-unknown-linux-gnu-objdump group.rv64clang.baseName=RISC-V rv64gc clang group.rv64clang.groupName=RISC-V 64 Clang +group.rv64clang.compilerCategories=clang compiler.rv64-clang900.exe=/opt/compiler-explorer/clang-9.0.0/bin/clang++ compiler.rv64-clang900.semver=9.0.0 @@ -703,6 +712,7 @@ group.wasmclang.supportsBinary=false group.wasmclang.licenseName=LLVM Apache 2 group.wasmclang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.wasmclang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.wasmclang.compilerCategories=clang compiler.wasm32clang.exe=/opt/compiler-explorer/clang-trunk/bin/clang++ compiler.wasm32clang.demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt @@ -719,6 +729,8 @@ group.icc.groupName=ICC x86-64 group.icc.baseName=x86-64 icc group.icc.isSemVer=true group.icc.licensePreamble=Proprietary, with thanks to Intel for the license +group.icc.compilerCategories=icc +group.icc.instructionSet=amd64 compiler.icc1301.exe=/opt/compiler-explorer/intel/bin/icc compiler.icc1301.alias=/opt/intel/bin/icc @@ -806,6 +818,8 @@ group.icx.compilerType=clang-intel group.icx.licenseName=LLVM Apache 2 group.icx.licenseLink=https://github.com/intel/llvm/blob/sycl/LICENSE.TXT group.icx.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.icx.compilerCategories=icc +group.icx.instructionSet=amd64 compiler.icx202112.exe=/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/bin/icpx compiler.icx202112.ldPath=/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/compiler/lib/intel64_lin:/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/lib @@ -877,6 +891,7 @@ group.cross.supportsExecute=false group.cross.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.cross.licenseName=GNU General Public License group.cross.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ +group.cross.compilerCategories=gcc ############################### # Cross for BPF @@ -893,6 +908,7 @@ group.clangbpf.isSemVer=true group.clangbpf.options=-target bpf group.clangbpf.objdumper=/opt/compiler-explorer/clang-trunk/bin/llvm-objdump group.clangbpf.objdumperType=llvm +group.clangbpf.compilerCategories=clang compiler.bpfclangtrunk.exe=/opt/compiler-explorer/clang-trunk/bin/clang++ compiler.bpfclangtrunk.semver=(trunk) @@ -1536,6 +1552,7 @@ group.mips-clang.supportsBinary=false group.mips-clang.supportsBinaryObject=false group.mips-clang.supportsExecute=false group.mips-clang.options=-target mips-elf +group.mips-clang.compilerCategories=clang compiler.mips-clang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ compiler.mips-clang1500.semver=15.0.0 @@ -1554,6 +1571,7 @@ group.mipsel-clang.supportsBinary=false group.mipsel-clang.supportsBinaryObject=false group.mipsel-clang.supportsExecute=false group.mipsel-clang.options=-target mipsel-elf +group.mipsel-clang.compilerCategories=clang compiler.mipsel-clang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ compiler.mipsel-clang1500.semver=15.0.0 @@ -1572,6 +1590,7 @@ group.mips64-clang.supportsBinary=false group.mips64-clang.supportsBinaryObject=false group.mips64-clang.supportsExecute=false group.mips64-clang.options=-target mips64-elf +group.mips64-clang.compilerCategories=clang compiler.mips64-clang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ compiler.mips64-clang1500.semver=15.0.0 @@ -1590,6 +1609,7 @@ group.mips64el-clang.supportsBinary=false group.mips64el-clang.supportsBinaryObject=false group.mips64el-clang.supportsExecute=false group.mips64el-clang.options=-target mips64el-elf +group.mips64el-clang.compilerCategories=clang compiler.mips64el-clang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ compiler.mips64el-clang1500.semver=15.0.0 @@ -1672,6 +1692,7 @@ compiler.mipselg1220.demangler=/opt/compiler-explorer/mipsel/gcc-12.2.0/mipsel-m group.mips64el.groupName=MIPS64EL GCC group.mips64el.compilers=mips564el:mips64elg1210:mips64elg1220 group.mips64el.baseName=mips64 (el) gcc +group.mips64el.compilerCategories=gcc compiler.mips564el.exe=/opt/compiler-explorer/mips64el/gcc-5.4.0/mips64el-unknown-linux-gnu/bin/mips64el-unknown-linux-gnu-g++ compiler.mips564el.semver=5.4 @@ -1882,6 +1903,8 @@ group.cl.demanglerType=win32 group.cl.supportsBinary=false group.cl.objdumper= group.cl.isSemVer=true +group.cl.compilerCategories=msvc +group.cl.instructionSet=amd64 group.cl19.groupName=WINE MSVC 2017 group.cl19.compilers=cl19_64:cl19_32:cl19_arm group.cl19.options=/I/opt/compiler-explorer/windows/10.0.10240.0/ucrt/ /I/opt/compiler-explorer/windows/19.10.25017/lib/native/include/ @@ -1895,7 +1918,7 @@ compiler.cl19_32.semver=19.10.25017 compiler.cl19_arm.exe=/opt/compiler-explorer/windows/19.10.25017/lib/native/bin/amd64_arm/cl.exe compiler.cl19_arm.name=ARM msvc v19.10 (WINE) compiler.cl19_arm.semver=19.10.25017 -compiler.cl19_arm.instructionSet=arm +compiler.cl19_arm.instructionSet=arm32 group.cl19_2015_u3.groupName=WINE MSVC 2015 group.cl19_2015_u3.compilers=cl19_2015_u3_64:cl19_2015_u3_32:cl19_2015_u3_arm @@ -1910,7 +1933,7 @@ compiler.cl19_2015_u3_32.semver=19.00.24210 compiler.cl19_2015_u3_arm.exe=/opt/compiler-explorer/windows/19.00.24210/bin/amd64_arm/cl.exe compiler.cl19_2015_u3_arm.name=ARM msvc v19.0 (WINE) compiler.cl19_2015_u3_arm.semver=19.00.24210 -compiler.cl19_2015_u3_arm.instructionSet=arm +compiler.cl19_2015_u3_arm.instructionSet=arm32 group.cl_new.groupName=WINE MSVC 2017 group.cl_new.compilers=cl_new_64:cl_new_32:cl_new_arm32:cl_new_arm64 @@ -2018,6 +2041,8 @@ group.cxx6502.supportsExecute=false group.cxx6502.licenseLink=https://github.com/lefticus/6502-cpp/blob/master/LICENSE group.cxx6502.licenseName=The Unlicense group.cxx6502.licensePreamble=This is free and unencumbered software released into the public domain. +group.cxx6502.compilerCategories=gcc +group.cxx6502.instructionSet=6502 compiler.gcc6502_1110.exe=/opt/compiler-explorer/6502-c++-trunk/bin/6502-c++ compiler.gcc6502_1110.semver=11.1.0 @@ -2042,6 +2067,7 @@ group.nvcxx_x86_cxx.demanglerType=nvhpc group.nvcxx_x86_cxx.groupName=nvc++ x86 group.nvcxx_x86_cxx.baseName=x86 nvc++ group.nvcxx_x86_cxx.isSemVer=true +group.nvcxx_x86_cxx.compilerCategories=nvc++ compiler.nvcxx_x86_cxx22_7.demangler=/opt/compiler-explorer/hpc_sdk/Linux_x86_64/22.7/compilers/bin/nvdecode compiler.nvcxx_x86_cxx22_7.exe=/opt/compiler-explorer/hpc_sdk/Linux_x86_64/22.7/compilers/bin/nvc++ @@ -2069,6 +2095,7 @@ group.nvcxx_arm_cxx.groupName=nvc++ arm group.nvcxx_arm_cxx.baseName=ARM64 nvc++ group.nvcxx_arm_cxx.isSemVer=true group.nvcxx_arm_cxx.instructionSet=aarch64 +group.nvcxx_arm_cxx.compilerCategories=nvc++ compiler.nvcxx_arm_cxx22_7.demangler=/opt/compiler-explorer/hpc_sdk/Linux_aarch64/22.7/compilers/bin/nvdecode compiler.nvcxx_arm_cxx22_7.exe=/opt/compiler-explorer/hpc_sdk/Linux_aarch64/22.7/compilers/bin/nvc++ diff --git a/etc/config/c++.defaults.properties b/etc/config/c++.defaults.properties index 98387fe0b..b06851816 100644 --- a/etc/config/c++.defaults.properties +++ b/etc/config/c++.defaults.properties @@ -2,6 +2,7 @@ compilers=&gcc:&clang group.gcc.compilers=g44:g45:g46:g47:g48:g5:g6x:g7:g8:g9:g10:g11:gdefault +group.gcc.compilerCategories=gcc compiler.g44.exe=/usr/bin/g++-4.4 compiler.g44.name=g++ 4.4 compiler.g45.exe=/usr/bin/g++-4.5 @@ -33,6 +34,7 @@ compiler.gdefault.name=g++ default group.clang.compilers=clang7:clang8:clang9:clang10:clang11:clang12:clangdefault group.clang.intelAsm=-mllvm --x86-asm-syntax=intel group.clang.compilerType=clang +group.clang.compilerCategories=clang compiler.clang7.exe=/usr/bin/clang++-7 compiler.clang7.name=clang 7 compiler.clang8.exe=/usr/bin/clang++-8 diff --git a/etc/config/c.amazon.properties b/etc/config/c.amazon.properties index 7502f4c66..655bd9060 100644 --- a/etc/config/c.amazon.properties +++ b/etc/config/c.amazon.properties @@ -23,6 +23,7 @@ group.cgcc86.supportsPVS-Studio=true group.cgcc86.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.cgcc86.licenseName=GNU General Public License group.cgcc86.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ +group.cgcc86.compilerCategories=gcc compiler.cg412.exe=/opt/compiler-explorer/gcc-4.1.2/bin/gcc compiler.cg412.semver=4.1.2 @@ -149,6 +150,8 @@ group.cgcc-classic.supportsExecute=false group.cgcc-classic.supportsBinary=false group.cgcc-classic.licenseName=GNU CC GENERAL PUBLIC LICENSE (Clarified 11 Feb 1988) group.cgcc-classic.supportsBinaryObject=true +group.cgcc-classic.compilerCategories=gcc +group.cgcc-classic.instructionSet=amd64 compiler.cg127.exe=/opt/compiler-explorer/gcc-1.27/bin/gcc compiler.cg127.semver=1.27 @@ -165,6 +168,7 @@ group.cclang.supportsPVS-Studio=true group.cclang.licenseName=LLVM Apache 2 group.cclang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.cclang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.cclang.compilerCategories=clang compiler.cclang30.exe=/opt/compiler-explorer/clang+llvm-3.0-x86_64-linux-Ubuntu-11_10/bin/clang compiler.cclang30.semver=3.0.0 @@ -297,6 +301,7 @@ group.armcclang32.instructionSet=arm32 group.armcclang32.licenseName=LLVM Apache 2 group.armcclang32.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.armcclang32.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.armcclang32.compilerCategories=clang group.armcclang64.groupName=Arm 64-bit clang group.armcclang64.compilers=armv8-cclang900:armv8-cclang901:armv8-cclang1000:armv8-cclang1001:armv8-cclang1100:armv8-cclang1101:armv8-cclang1200:armv8-cclang1201:armv8-cclang1300:armv8-cclang1301:armv8-cclang1400:armv8-cclang1500:armv8-cclang-trunk:armv8-full-cclang-trunk @@ -307,7 +312,7 @@ group.armcclang64.instructionSet=aarch64 group.armcclang64.licenseName=LLVM Apache 2 group.armcclang64.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.armcclang64.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions - +group.armcclang64.compilerCategories=clang compiler.armv7-cclang1500.name=armv7-a clang 15.0.0 compiler.armv7-cclang1500.exe=/opt/compiler-explorer/clang-15.0.0/bin/clang @@ -512,6 +517,7 @@ group.cmosclang-trunk.compilerType=llvmmos group.cmosclang-trunk.isSemVer=true group.cmosclang-trunk.objdumper=/opt/compiler-explorer/llvm-mos-trunk/bin/llvm-objdump group.cmosclang-trunk.objdumperType=llvm +group.cmosclang-trunk.compilerCategories=clang compiler.cmos-nes-cnrom-trunk.exe=/opt/compiler-explorer/llvm-mos-trunk/bin/mos-nes-cnrom-clang compiler.cmos-nes-cnrom-trunk.semver=nes-cnrom @@ -551,6 +557,7 @@ group.rvcclang.isSemVer=true group.rvcclang.licenseName=LLVM Apache 2 group.rvcclang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.rvcclang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.rvcclang.compilerCategories=clang group.rv32cclang.compilers=rv32-cclang:rv32-cclang1500:rv32-cclang1400:rv32-cclang1301:rv32-cclang1300:rv32-cclang1200:rv32-cclang1201:rv32-cclang1101:rv32-cclang1100:rv32-cclang1001:rv32-cclang1000:rv32-cclang901:rv32-cclang900 group.rv32cclang.options=-target riscv32-unknown-elf -march=rv32gc -mabi=ilp32d --gcc-toolchain=/opt/compiler-explorer/riscv32/gcc-10.2.0/riscv32-unknown-elf @@ -560,6 +567,7 @@ group.rv32cclang.groupName=RISC-V 32 Clang group.rv32cclang.licenseName=LLVM Apache 2 group.rv32cclang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.rv32cclang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.rv32cclang.compilerCategories=clang compiler.rv32-cclang900.exe=/opt/compiler-explorer/clang-9.0.0/bin/clang compiler.rv32-cclang900.semver=9.0.0 @@ -602,6 +610,7 @@ group.rv64cclang.options=-target riscv64-unknown-linux-gnu -march=rv64gc -mabi=l group.rv64cclang.objdumper=/opt/compiler-explorer/riscv64/gcc-8.2.0/riscv64-unknown-linux-gnu/bin/riscv64-unknown-linux-gnu-objdump group.rv64cclang.baseName=RISC-V rv64gc clang group.rv64cclang.groupName=RISC-V 64 Clang +group.rv64cclang.compilerCategories=clang compiler.rv64-cclang900.semver=9.0.0 compiler.rv64-cclang900.exe=/opt/compiler-explorer/clang-9.0.0/bin/clang @@ -648,6 +657,7 @@ group.wasmcclang.supportsBinaryObject=false group.wasmcclang.licenseName=LLVM Apache 2 group.wasmcclang.licenseLink=https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT group.wasmcclang.licensePreamble=The LLVM Project is under the Apache License v2.0 with LLVM Exceptions +group.wasmcclang.compilerCategories=clang compiler.wasm32cclang.exe=/opt/compiler-explorer/clang-trunk/bin/clang compiler.wasm32cclang.demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt @@ -677,6 +687,8 @@ group.cicc.options=-x c -gcc-name=/opt/compiler-explorer/gcc-4.7.1/bin/gcc group.cicc.groupName=ICC x86-64 group.cicc.isSemVer=true group.cicc.baseName=x86-64 icc +group.cicc.compilerCategories=icc +group.cicc.instructionSet=amd64 compiler.cicc1301.exe=/opt/compiler-explorer/intel/bin/icc compiler.cicc1301.semver=13.0.1 # intel 13.01 binary disabled: it just segfaults (no idea why) @@ -754,6 +766,8 @@ group.cicx.groupName=ICX x86-64 group.cicx.baseName=x86-64 icx group.cicx.isSemVer=true group.cicx.compilerType=clang-intel +group.cicx.compilerCategories=icc +group.cicx.instructionSet=amd64 compiler.cicx202112.exe=/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/bin/icx compiler.cicx202112.ldPath=/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/compiler/lib/intel64_lin:/opt/compiler-explorer/intel-cpp-2021.1.2.63/compiler/latest/linux/lib @@ -812,6 +826,7 @@ group.ccross.groupName=Cross GCC group.ccross.licenseLink=https://gcc.gnu.org/onlinedocs/gcc/Copying.html group.ccross.licenseName=GNU General Public License group.ccross.licensePreamble=Copyright (c) 2007 Free Software Foundation, Inc. https://fsf.org/ +group.ccross.compilerCategories=gcc ############################### # Cross for BPF @@ -1166,7 +1181,7 @@ compiler.carm1031_07.supportsBinary=false compiler.carm1031_10.exe=/opt/compiler-explorer/arm/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc compiler.carm1031_10.name=ARM gcc 10.3.1 (2021.10 none) compiler.carm1031_10.semver=10.3.1 -compiler.carm1031_10.supportsBinary=false +compiler.carm1031_10.supportsBinary=false compiler.carm1121.exe=/opt/compiler-explorer/arm/gcc-arm-none-eabi-11.2-2022.02/bin/arm-none-eabi-gcc compiler.carm1121.name=ARM gcc 11.2.1 (none) compiler.carm1121.semver=11.2.1 @@ -1867,6 +1882,8 @@ group.ccl.versionRe=^Microsoft \(R\) C/C\+\+.*$ group.ccl.isSemVer=true group.ccl.supportsBinary=false group.ccl.objdumper= +group.ccl.compilerCategories=msvc +group.ccl.instructionSet=amd64 group.ccl19.groupName=WINE MSVC 2017 group.ccl19.compilers=ccl19_64:ccl19_32:ccl19_arm group.ccl19.options=/I/opt/compiler-explorer/windows/10.0.10240.0/ucrt/ /I/opt/compiler-explorer/windows/19.10.25017/lib/native/include/ /TC @@ -1879,7 +1896,7 @@ compiler.ccl19_32.semver=19.10.25017 compiler.ccl19_arm.exe=/opt/compiler-explorer/windows/19.10.25017/lib/native/bin/amd64_arm/cl.exe compiler.ccl19_arm.name=ARM msvc v19.10 (WINE) compiler.ccl19_arm.semver=19.10.25017 -compiler.ccl19_arm.instructionSet=arm +compiler.ccl19_arm.instructionSet=arm32 group.ccl19_2015_u3.groupName=WINE MSVC 2015 group.ccl19_2015_u3.compilers=ccl19_2015_u3_64:ccl19_2015_u3_32:ccl19_2015_u3_arm @@ -1893,7 +1910,7 @@ compiler.ccl19_2015_u3_32.semver=19.00.24210 compiler.ccl19_2015_u3_arm.exe=/opt/compiler-explorer/windows/19.00.24210/bin/amd64_arm/cl.exe compiler.ccl19_2015_u3_arm.name=ARM msvc v19.0 (WINE) compiler.ccl19_2015_u3_arm.semver=19.00.24210 -compiler.ccl19_2015_u3_arm.instructionSet=arm +compiler.ccl19_2015_u3_arm.instructionSet=arm32 group.ccl_new.groupName=WINE MSVC 2017 group.ccl_new.compilers=ccl_new_64:ccl_new_32:ccl_new_arm32:ccl_new_arm64 diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 9beb41bd1..a306ddabd 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -37,6 +37,11 @@ export function unique(arr: V[]): V[] { return [...new Set(arr)]; } +export function intersection(a: V[], b: V[]): V[] { + const B = new Set(b); + return [...a].filter(item => B.has(item)); +} + // arr.filter(x => x !== null) returns a (T | null)[] even though it is a T[] // Apparently the idiomatic solution is arr.filter((x): x is T => x !== null), but this is shorter (and the type // predicate also isn't type checked so it doesn't seem safe to me) diff --git a/lib/compiler-finder.ts b/lib/compiler-finder.ts index 88bece6cb..8ef3760b6 100644 --- a/lib/compiler-finder.ts +++ b/lib/compiler-finder.ts @@ -261,6 +261,7 @@ export class CompilerFinder { versionRe: props('versionRe'), explicitVersion: props('explicitVersion'), compilerType: props('compilerType', ''), + compilerCategories: props('compilerCategories', undefined)?.split(':'), debugPatched: props('debugPatched', false), demangler: demangler, demanglerType: props('demanglerType', ''), @@ -269,7 +270,7 @@ export class CompilerFinder { objdumperType: props('objdumperType', ''), intelAsm: props('intelAsm', ''), supportsAsmDocs: props('supportsAsmDocs', true), - instructionSet: props('instructionSet', ''), + instructionSet: props('instructionSet', '').toString(), needsMulti: !!props('needsMulti', true), adarts: props('adarts', ''), supportsDemangle: !!demangler, diff --git a/package-lock.json b/package-lock.json index 269cd1daa..6ad2bf937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@aws-sdk/client-ssm": "^3.282.0", "@flatten-js/interval-tree": "^1.0.20", "@fortawesome/fontawesome-free": "^6.2.1", + "@orchidjs/sifter": "^1.0.3", "@sentry/browser": "^7.28.1", "@sentry/node": "^7.28.1", "@types/morgan": "^1.9.4", @@ -2991,9 +2992,17 @@ } }, "node_modules/@orchidjs/sifter": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-0.9.3.tgz", - "integrity": "sha512-9XGiAJcWvEektxjR9X+dYeG+kg8GdojM6ZCmrnuDaWoVew+GAKxiesvYxyY+RLUgWTv8B1qpLIVYlswxKcW5XQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz", + "integrity": "sha512-zCZbwKegHytfsPm8Amcfh7v/4vHqTAaOu6xFswBYcn8nznBOuseu6COB2ON7ez0tFV0mKL0nRNnCiZZA+lU9/g==", + "dependencies": { + "@orchidjs/unicode-variants": "^1.0.4" + } + }, + "node_modules/@orchidjs/unicode-variants": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz", + "integrity": "sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ==" }, "node_modules/@pkgr/utils": { "version": "2.3.1", @@ -15356,6 +15365,11 @@ "url": "https://opencollective.com/tom-select" } }, + "node_modules/tom-select/node_modules/@orchidjs/sifter": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-0.9.3.tgz", + "integrity": "sha512-9XGiAJcWvEektxjR9X+dYeG+kg8GdojM6ZCmrnuDaWoVew+GAKxiesvYxyY+RLUgWTv8B1qpLIVYlswxKcW5XQ==" + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", diff --git a/package.json b/package.json index 88092b882..ff61726a8 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@aws-sdk/client-ssm": "^3.282.0", "@flatten-js/interval-tree": "^1.0.20", "@fortawesome/fontawesome-free": "^6.2.1", + "@orchidjs/sifter": "^1.0.3", "@sentry/browser": "^7.28.1", "@sentry/node": "^7.28.1", "@types/morgan": "^1.9.4", diff --git a/static/.eslint-ce-static.yml b/static/.eslint-ce-static.yml index 65798aebc..42ee37727 100644 --- a/static/.eslint-ce-static.yml +++ b/static/.eslint-ce-static.yml @@ -27,9 +27,9 @@ rules: - error - smart indent: - - error - - 4 - - SwitchCase: 1 + - off + #- 4 + #- SwitchCase: 1 max-len: - error - 120 diff --git a/static/highlight.ts b/static/highlight.ts new file mode 100644 index 000000000..7c53744ad --- /dev/null +++ b/static/highlight.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2023, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +type Interval = {start: number; length: number}; + +function regexExecAll(base_re: RegExp, s: string) { + const re = new RegExp(base_re.source, base_re.flags + 'gd'); + let m: any; + const matches: Interval[] = []; + while ((m = re.exec(s)) != null) { + // TODO(jeremy-rifkin): Find a way to get TS to understand that m.indices is real + matches.push({ + start: m.indices[0][0], + length: m.indices[0][1] - m.indices[0][0], + }); + } + return matches; +} + +export function highlight(str: string, regexes: RegExp[]) { + // At the moment, because compiler names are short, I think this solution is best. It's easiest to just + // track intervals with an array. The ideal solution in the general case is probably something along the + // lines of an interval tree. + const intervals: Interval[] = []; + for (const regex of regexes) { + intervals.push(...regexExecAll(regex, str)); + } + // sort by interval start + intervals.sort((a, b) => a.start - b.start); + // combine intervals + let i = 0; + while (i < intervals.length - 1) { + const {start: AStart, length: ALength} = intervals[i]; + const {start: BStart, length: BLength} = intervals[i + 1]; + if (AStart + ALength >= BStart) { + intervals.splice(i, 2, { + start: AStart, + length: BStart + BLength - AStart, + }); + } else { + i++; + } + } + // for each interval, highlight + let offset = 0; + for (const {start, length} of intervals) { + const tagStart = ''; + const tagEnd = ''; + const intervalStart = offset + start; + const intervalEnd = offset + start + length; + str = + str.slice(0, intervalStart) + + tagStart + + str.slice(intervalStart, intervalEnd) + + tagEnd + + str.slice(intervalEnd); + offset += tagStart.length + tagEnd.length; + } + return str; +} diff --git a/static/panes/compiler.ts b/static/panes/compiler.ts index ddcb4a45e..598e8dd1f 100644 --- a/static/panes/compiler.ts +++ b/static/panes/compiler.ts @@ -234,7 +234,8 @@ export class Compiler extends MonacoPane | null; private revealJumpStack: (monaco.editor.ICodeEditorViewState | null)[]; - private compilerPicker: CompilerPicker | JQuery; + private compilerPickerElement: JQuery; + private compilerPicker: CompilerPicker; private compiler: CompilerInfo | null; private currentLangId: string | null; private filters: Toggles; @@ -470,6 +471,7 @@ export class Compiler extends MonacoPane { this.compilerPickers = _.reject(this.compilerPickers, function (entry) { return compilerEntry.picker?.id === entry.picker?.id; }); - compilerEntry.picker?.tomSelect?.close(); + compilerEntry.picker?.destroy(); compilerEntry.parent.remove(); this.updateLibraries(); @@ -596,7 +596,7 @@ export class Conformance extends Pane { override close(): void { this.eventHub.unsubscribe(); this.compilerPickers.forEach(compilerEntry => { - compilerEntry.picker?.tomSelect?.close(); + compilerEntry.picker?.destroy(); compilerEntry.parent.remove(); }); if (this.compilerInfo.editorId) this.eventHub.emit('conformanceViewClose', this.compilerInfo.editorId); diff --git a/static/panes/executor.ts b/static/panes/executor.ts index 4882b8803..deded9f1a 100644 --- a/static/panes/executor.ts +++ b/static/panes/executor.ts @@ -284,7 +284,7 @@ export class Executor extends Pane { close(): void { this.eventHub.unsubscribe(); if (this.compilerPicker instanceof CompilerPicker) { - this.compilerPicker.close(); + this.compilerPicker.destroy(); } this.eventHub.emit('executorClose', this.id); diff --git a/static/styles/explorer.scss b/static/styles/explorer.scss index e6d5a4d9f..cb67fafc7 100644 --- a/static/styles/explorer.scss +++ b/static/styles/explorer.scss @@ -547,12 +547,12 @@ input.vim-check { height: 20px; } -/* While we find a better solution, lets up this a bit */ -.compiler-picker .ts-dropdown-content { - max-height: 300px !important; +.compiler-picker-dropdown .ts-dropdown-content { + // Default max-height, the compiler picker will override this to prevent overflowing the window + max-height: 600px; } -.compiler-picker .ts-dropdown-content .option { +.compiler-picker-dropdown .ts-dropdown-content .option { padding: 0 10px; } @@ -834,6 +834,105 @@ div.populararguments div.dropdown-menu { display: none; } +@media (max-width: 830px) { + #compiler-picker-modal { + .compilers-row { + flex-direction: column-reverse; + } + } +} + +@media (max-width: 1200px) { + #compiler-picker-modal { + .compilers-col { + .compilers { + width: 300px !important; + columns: 1 300px !important; + } + } + } +} + +#compiler-picker-modal { + .modal-body { + overflow-y: scroll; + } + + input.compiler-search { + margin-left: 10px; + //outline: 0 !important; + padding: 5px 10px; + } + + .filters { + margin-top: 10px; + } + + .architecture, + .compiler-type { + padding: 2px 5px; + border-radius: 3px; + margin: 0 0 0 3px; + cursor: pointer; + user-select: none; + } + + .compilers-col { + margin-top: 5px; + h6 { + margin-top: 15px; + font-size: 16px; + } + .compilers { + width: 650px; + columns: 2 300px; + column-gap: 40px; + &.one-col { + width: 300px; + columns: 1 300px; + } + } + .favorites { + margin-left: 10px; + } + .compilers, + .favorites { + .group-wrapper { + display: inline-block; + .group { + width: 300px; + margin-bottom: 25px; + .label { + cursor: pointer; + } + .compiler { + cursor: pointer; + .highlight { + background: rgba(255, 237, 40, 0.4); + border-radius: 1px; + } + .toggle-fav { + cursor: pointer; + } + } + .folded { + display: none; + text-align: center; + } + &.collapsed { + .compiler { + display: none !important; + } + .folded { + display: block !important; + } + } + } + } + } + } +} + #timing-info .modal-content { min-width: 500px; min-height: 400px; @@ -939,11 +1038,35 @@ html[data-theme='dark'] { overflow-y: scroll; } -.prepend-options { +.prepend-options, +.picker-popout-button { border-top-right-radius: 0; border-bottom-right-radius: 0; } +.picker-popout-button { + font-size: 14px; + line-height: 14px; + position: relative; + i { + vertical-align: middle; + } +} + +.compiler-picker-dropdown-popout-wrapper { + text-align: center; + cursor: pointer; + font-size: 15px; + position: absolute; + top: 100%; + background: inherit; + box-shadow: rgba(0, 0, 0, 0.176) 0px 6px 12px 0px; // same as the tomselect dropdown + left: 0; + .compiler-picker-dropdown-popout { + padding: 10px; + } +} + .popular-arguments-btn { border-top-right-radius: 0; } diff --git a/static/styles/themes/dark-theme.scss b/static/styles/themes/dark-theme.scss index bb747974a..c7caf47b9 100644 --- a/static/styles/themes/dark-theme.scss +++ b/static/styles/themes/dark-theme.scss @@ -507,6 +507,14 @@ textarea.form-control { background-color: #353535 !important; } +.picker-popout-button { + border: none !important; + background: #444444 !important; + &:hover { + background: darken(#444444, 5%) !important; + } +} + .qb-logo-pri { fill: #e2e2e2 !important; } @@ -622,6 +630,30 @@ textarea.form-control { } } +#compiler-picker-modal { + .architecture, + .compiler-type { + background: #444; + &.active { + background: #007bff; + } + } + + .compilers-col .group-wrapper .group { + .label { + background: #555555; + } + .compiler { + &:hover { + background: #8d8d8e; + } + &.selected { + background: #21525f; + } + } + } +} + #alert { &.error-alert { .modal-content { diff --git a/static/styles/themes/default-theme.scss b/static/styles/themes/default-theme.scss index 9fa52a608..59e9275f4 100644 --- a/static/styles/themes/default-theme.scss +++ b/static/styles/themes/default-theme.scss @@ -278,6 +278,11 @@ kbd { background-color: #e9ecef; } +.picker-popout-button { + background-color: #fff; + border-left: none; +} + #simplecook { background-color: #f4f4f4; } @@ -410,6 +415,31 @@ div.argmenuitem span.argdescription { } } +#compiler-picker-modal { + .architecture, + .compiler-type { + background: #eeeeee; + &.active { + background: #3c9aff; + } + } + + .compilers-col .group-wrapper .group { + .label { + // taking the color tom-select uses, but I find that a little low-contrast + background: darken(#f6f6f6, 3%); + } + .compiler { + &:hover { + background: #e9ecef; + } + &.selected { + background: #e9ecef; + } + } + } +} + #alert { &.error-alert { .modal-content { diff --git a/static/widgets/compiler-picker-popup.ts b/static/widgets/compiler-picker-popup.ts new file mode 100644 index 000000000..99d01d8d9 --- /dev/null +++ b/static/widgets/compiler-picker-popup.ts @@ -0,0 +1,269 @@ +// Copyright (c) 2023, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import $ from 'jquery'; +import _ from 'underscore'; + +import * as sifter from '@orchidjs/sifter'; + +import {CompilerInfo} from '../../types/compiler.interfaces'; +import {intersection, remove, unique} from '../../lib/common-utils'; +import {unwrap, unwrapString} from '../assert'; +import {CompilerPicker} from './compiler-picker'; +import {CompilerService} from '../compiler-service'; +import {highlight} from '../highlight'; + +export class CompilerPickerPopup { + modal: JQuery; + searchBar: JQuery; + architectures: JQuery; + compilerTypes: JQuery; + compilersContainer: JQuery; + resultsContainer: JQuery; + favoritesContainer: JQuery; + + groups: {value: string; label: string}[]; + options: (CompilerInfo & {$groups: string[]})[]; + langId: string; + + isaFilters: string[]; + categoryFilters: string[]; + + sifter: sifter.Sifter; + searchResults: ReturnType | undefined; + + constructor(private readonly compilerPicker: CompilerPicker) { + this.modal = $('#compiler-picker-modal').clone(true); + this.searchBar = this.modal.find('.compiler-search'); + this.architectures = this.modal.find('.architectures'); + this.compilerTypes = this.modal.find('.compiler-types'); + this.compilersContainer = this.modal.find('.compilers-row'); + this.resultsContainer = this.modal.find('.compilers'); + this.favoritesContainer = this.modal.find('.favorites'); + + this.modal.on('shown.bs.modal', () => { + this.searchBar[0].focus(); + }); + } + + setLang(groups: {value: string; label: string}[], options: (CompilerInfo & {$groups: string[]})[], langId: string) { + this.groups = groups; + this.options = options; + this.langId = langId; + this.setupFilters(); + this.sifter = new sifter.Sifter(options, { + diacritics: false, + }); + } + + setupFilters() { + // get available instruction sets + const compilers = Object.values(this.compilerPicker.compilerService.getCompilersForLang(this.langId) ?? {}); + // If instructionSet is '', just label it unknown + const instruction_sets = compilers.map(compiler => compiler.instructionSet || 'other'); + this.architectures.empty(); + this.architectures.append( + ...unique(instruction_sets) + .sort() + .map(isa => `${_.escape(isa)}`), + ); + // get available compiler types + const compilerTypes = compilers.map(compiler => compiler.compilerCategories ?? ['other']).flat(); + this.compilerTypes.empty(); + this.compilerTypes.append( + ...unique(compilerTypes) + .sort() + .map(type => `${_.escape(type)}`), + ); + + // search box + this.searchBar.on('input', () => { + const query = unwrapString(this.searchBar.val()).trim(); + if (query === '') { + this.searchResults = undefined; + } else { + this.searchResults = this.sifter.search(query, { + fields: ['name'], + conjunction: 'and', + sort: CompilerService.getSelectizerOrder(), + }); + } + this.fillCompilers(); + }); + + // isa filters + $(this.architectures) + .find('.architecture') + .on('click', e => this.onFilterClick(e, this.isaFilters)); + + // category filters + $(this.compilerTypes) + .find('.compiler-type') + .on('click', e => this.onFilterClick(e, this.categoryFilters)); + } + + onFilterClick(e: JQuery.ClickEvent, filtersArray: string[]) { + e.preventDefault(); + const elem = $(e.currentTarget); + elem.toggleClass('active'); + const filterValue = unwrap(elem.attr('data-value')); + if (filtersArray.includes(filterValue)) { + // This is pretty much the best way to filter an array in-place + filtersArray.splice(0, filtersArray.length, ...filtersArray.filter(v => v !== filterValue)); + } else { + filtersArray.push(filterValue); + } + this.fillCompilers(); + } + + fillCompilers() { + const filteredIndices = this.searchResults + ? new Set(this.searchResults.items.map(item => item.id as number)) + : undefined; + const filteredCompilers = this.options.filter((compiler, i) => { + if (this.isaFilters.length > 0) { + if (!this.isaFilters.includes(compiler.instructionSet || 'other')) { + return false; + } + } + if (this.categoryFilters.length > 0) { + const categories = compiler.compilerCategories ?? ['other']; + if (intersection(this.categoryFilters, categories).length === 0) { + return false; + } + } + if (filteredIndices) { + if (!filteredIndices.has(i)) { + return false; + } + } + return true; + }); + // figure out if there are any empty groups, these will be ignored + const groupCounts: Partial> = {}; + for (const compiler of filteredCompilers) { + for (const group of compiler.$groups) { + groupCounts[group] = (groupCounts[group] ?? 0) + 1; + } + } + // add the compiler entries / group headers themselves + this.resultsContainer.empty(); + this.favoritesContainer.empty(); + const groupMap: Record = {}; + for (const group of this.groups) { + if ((groupCounts[group.value] ?? 0) > 0 || group.value === CompilerPicker.favoriteGroupName) { + const group_elem = $( + ` +
+
+
${_.escape(group.label)}
+
+
+ `, + ); + if (group.value === CompilerPicker.favoriteGroupName) { + group_elem.appendTo(this.favoritesContainer); + } else { + group_elem.appendTo(this.resultsContainer); + } + groupMap[group.value] = group_elem.find('.group'); + } + } + // if there can only ever be one column, don't bother with room for 2 + this.resultsContainer.toggleClass( + 'one-col', + this.groups.filter(group => group.value !== CompilerPicker.favoriteGroupName).length <= 1, + ); + const searchRegexes = this.searchResults + ? remove( + this.searchResults.tokens.map(token => token.regex), + null, + ) + : undefined; + for (const compiler of filteredCompilers) { + const isFavorited = compiler.$groups.includes(CompilerPicker.favoriteGroupName); + const extraClasses = isFavorited ? 'fas fa-star fav' : 'far fa-star'; + for (const group of compiler.$groups) { + // TODO(jeremy-rifkin): At the moment none of our compiler names should contain html special characters. + // This is just a good measure to take. If a compiler is ever added that does have special characters in + // its name it could interfere with the highlighting (e.g. if your text search is for "<" that won't + // highlight). I'm going to defer handling that to a future PR though. + const name = _.escape(compiler.name); + const compiler_elem = $( + ` +
+
${searchRegexes ? highlight(name, searchRegexes) : name}
+
+ +
+
+ `, + ); + if (compiler.id === this.compilerPicker.lastCompilerId) { + compiler_elem.addClass('selected'); + } + compiler_elem.appendTo(groupMap[group]); + } + } + // group header click events + this.compilersContainer.find('.group').append('
'); + this.compilersContainer.find('.group > .label').on('click', e => { + $(e.currentTarget).closest('.group').toggleClass('collapsed'); + }); + // compiler click events + this.compilersContainer.find('.compiler').on('click', e => { + this.compilerPicker.selectCompiler(unwrap(e.currentTarget.getAttribute('data-value'))); + this.hide(); + }); + // favorite stars + this.compilersContainer.find('.compiler .toggle-fav').on('click', e => { + const compilerId = unwrap($(e.currentTarget).closest('.compiler').attr('data-value')); + const data = filteredCompilers.filter(c => c.id === compilerId)[0]; + const isAddingNewFavorite = !data.$groups.includes(CompilerPicker.favoriteGroupName); + if (isAddingNewFavorite) { + data.$groups.push(CompilerPicker.favoriteGroupName); + this.compilerPicker.addToFavorites(data.id); + } else { + data.$groups.splice(data.$groups.indexOf(CompilerPicker.favoriteGroupName), 1); + this.compilerPicker.removeFromFavorites(data.id); + } + this.fillCompilers(); + }); + } + + show() { + // reflow the compilers to get any new favorites from the compiler picker dropdown and reset filters and whatnot + this.isaFilters = []; + this.categoryFilters = []; + this.searchBar.val(''); + this.searchBar.trigger('input'); + this.modal.find('.architectures .active, .compiler-types .active').toggleClass('active'); + this.fillCompilers(); + this.modal.modal({}); + } + + hide() { + this.modal.modal('hide'); + } +} diff --git a/static/widgets/compiler-picker.ts b/static/widgets/compiler-picker.ts index 2af44423c..36c8dbc81 100644 --- a/static/widgets/compiler-picker.ts +++ b/static/widgets/compiler-picker.ts @@ -30,6 +30,10 @@ import * as local from '../local.js'; import {EventHub} from '../event-hub.js'; import {Hub} from '../hub.js'; import {CompilerService} from '../compiler-service.js'; +import {CompilerInfo} from '../../types/compiler.interfaces.js'; +import {unique} from '../../lib/common-utils.js'; +import {unwrap} from '../assert.js'; +import {CompilerPickerPopup} from './compiler-picker-popup.js'; type Favourites = { [compilerId: string]: boolean; @@ -39,7 +43,6 @@ export class CompilerPicker { static readonly favoriteGroupName = '__favorites__'; static readonly favoriteStoreKey = 'favCompilerIds'; static nextSelectorId = 1; - domRoot: JQuery; domNode: HTMLSelectElement; eventHub: EventHub; id: number; @@ -49,6 +52,9 @@ export class CompilerPicker { lastLangId: string; lastCompilerId: string; compilerIsVisible: (any) => any; // TODO => bool probably + popup: CompilerPickerPopup; + toolbarPopoutButton: JQuery; + popupTooltip: JQuery; constructor( domRoot: JQuery, hub: Hub, @@ -63,6 +69,11 @@ export class CompilerPicker { if (!(compilerPicker instanceof HTMLSelectElement)) { throw new Error('.compiler-picker is not an HTMLSelectElement'); } + this.toolbarPopoutButton = domRoot.find('.picker-popout-button'); + domRoot.find('.picker-popout-button').on('click', () => { + unwrap(this.tomSelect).close(); + this.popup.show(); + }); this.domNode = compilerPicker; this.compilerService = hub.compilerService; this.onCompilerChange = onCompilerChange; @@ -74,22 +85,24 @@ export class CompilerPicker { this.compilerIsVisible = () => true; } + this.popup = new CompilerPickerPopup(this); + this.initialize(langId, compilerId); } - close() { - // Quick note while I'm here: This function is never called. It probably should be. The conformance view - // bypasses this function and does compilerEntry.picker.tomSelect.close(); manually. This function is the - // only time this.tomSelect can be null, might be nice if we can get rid of that. + destroy() { this.eventHub.unsubscribe(); if (this.tomSelect) this.tomSelect.destroy(); this.tomSelect = null; } - initialize(langId: string, compilerId: string) { + private initialize(langId: string, compilerId: string) { this.lastLangId = langId; this.lastCompilerId = compilerId; + const groups = this.getGroups(langId); + const options = this.getOptions(langId, compilerId); + this.tomSelect = new TomSelect(this.domNode, { sortField: CompilerService.getSelectizerOrder(), valueField: 'id', @@ -97,9 +110,9 @@ export class CompilerPicker { searchField: ['name'], placeholder: '🔍 Select a compiler...', optgroupField: '$groups', - optgroups: this.getGroups(langId), + optgroups: groups, lockOptgroupOrder: true, - options: this.getOptions(langId, compilerId), + options: options, items: compilerId ? [compilerId] : [], dropdownParent: 'body', closeAfterSelect: true, @@ -110,14 +123,14 @@ export class CompilerPicker { // Typing here needs improvement later anyway. /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */ if (val) { + const compilerId = val as any as string; ga.proxy('send', { hitType: 'event', eventCategory: 'SelectCompiler', - eventAction: val, + eventAction: compilerId, }); - const str = val as any as string; - this.onCompilerChange(str); - this.lastCompilerId = str; + this.onCompilerChange(compilerId); + this.lastCompilerId = compilerId; } }, duplicates: true, @@ -140,6 +153,32 @@ export class CompilerPicker { }, }); + this.tomSelect.on('dropdown_open', () => { + // Prevent overflowing the window + const dropdown = unwrap(this.tomSelect).dropdown_content; + dropdown.style.maxHeight = `${window.innerHeight - dropdown.getBoundingClientRect().top - 10}px`; + + this.popupTooltip = $(` +
+
+ Pop Out +
+
+ `); + // I think tomselect is stealing the click event here. Somehow tomselect's global onclick prevents a click + // here from firing, maybe related to the dropdown closing and this getting removed from the dom. But, + // mousedown is a decent workaround. + this.popupTooltip.on('mousedown', () => { + unwrap(this.tomSelect).close(); + this.popup.show(); + }); + this.popupTooltip.appendTo(this.toolbarPopoutButton); + }); + + this.tomSelect.on('dropdown_close', () => { + this.popupTooltip.remove(); + }); + $(this.tomSelect.dropdown_content).on('click', '.toggle-fav', evt => { evt.preventDefault(); evt.stopPropagation(); @@ -156,13 +195,10 @@ export class CompilerPicker { data.$groups.push(CompilerPicker.favoriteGroupName); this.addToFavorites(data.id); } else { - data.$groups.splice(data.group.indexOf(CompilerPicker.favoriteGroupName), 1); + data.$groups.splice(data.$groups.indexOf(CompilerPicker.favoriteGroupName), 1); this.removeFromFavorites(data.id); } - this.tomSelect.updateOption(value, data); - this.tomSelect.refreshOptions(false); - if (clickedGroup !== CompilerPicker.favoriteGroupName) { // If the user clicked on an option that wasn't in the top "Favorite" group, then we just added // or removed a bunch of controls way up in the list. Find the new element top and adjust the scroll @@ -175,16 +211,21 @@ export class CompilerPicker { } } }); + + this.popup.setLang(groups, options, langId); } - getOptions(langId: string, compilerId: string) { + getOptions(langId: string, compilerId: string): (CompilerInfo & {$groups: string[]})[] { const favorites = this.getFavorites(); return Object.values(this.compilerService.getCompilersForLang(langId) ?? {}) .filter(e => (this.compilerIsVisible(e) && !e.hidden) || e.id === compilerId) .map(e => { - e.$groups = [e.group]; - if (favorites[e.id]) e.$groups.unshift(CompilerPicker.favoriteGroupName); - return e; + const $groups = [e.group]; + if (favorites[e.id]) $groups.unshift(CompilerPicker.favoriteGroupName); + return { + ...e, + $groups, + }; }); } @@ -225,6 +266,7 @@ export class CompilerPicker { const faves = this.getFavorites(); faves[compilerId] = true; this.setFavorites(faves); + this.updateTomselectOption(compilerId); this.eventHub.emit('compilerFavoriteChange', this.id); } @@ -232,6 +274,20 @@ export class CompilerPicker { const faves = this.getFavorites(); delete faves[compilerId]; this.setFavorites(faves); + this.updateTomselectOption(compilerId); this.eventHub.emit('compilerFavoriteChange', this.id); } + + updateTomselectOption(compilerId: string) { + if (this.tomSelect) { + this.tomSelect.updateOption(compilerId, this.tomSelect.options[compilerId]); + this.tomSelect.refreshOptions(false); + } + } + + selectCompiler(compilerId: string) { + if (this.tomSelect) { + this.tomSelect.addItem(compilerId); + } + } } diff --git a/types/compiler.interfaces.ts b/types/compiler.interfaces.ts index 939c31057..93507342f 100644 --- a/types/compiler.interfaces.ts +++ b/types/compiler.interfaces.ts @@ -40,6 +40,9 @@ export type CompilerInfo = { versionRe?: string; explicitVersion?: string; compilerType: string; + // groups are more fine-grained, e.g. gcc x86-64, gcc arm, clang x86-64, ... + // category is more broad: gcc, clang, msvc, ... + compilerCategories?: string[]; debugPatched: boolean; demangler: string; demanglerType: string; @@ -79,7 +82,6 @@ export type CompilerInfo = { lang: LanguageKey; group: string; groupName: string; - $groups: string[]; includeFlag: string; includePath: string; linkFlag: string; @@ -123,10 +125,9 @@ export type CompilerInfo = { }; // Compiler information collected by the compiler-finder -export type PreliminaryCompilerInfo = Omit< - CompilerInfo, - 'version' | 'fullVersion' | 'baseName' | '$groups' | 'disabledFilters' -> & {version?: string}; +export type PreliminaryCompilerInfo = Omit & { + version?: string; +}; export interface ICompiler { possibleArguments: ICompilerArguments; diff --git a/views/popups/_all.pug b/views/popups/_all.pug index 5fa750e2f..00187bab2 100644 --- a/views/popups/_all.pug +++ b/views/popups/_all.pug @@ -2,6 +2,8 @@ include load-save include alert +include compiler-picker-modal + include yes-no include enter-something diff --git a/views/popups/compiler-picker-modal.pug b/views/popups/compiler-picker-modal.pug new file mode 100644 index 000000000..97dafe668 --- /dev/null +++ b/views/popups/compiler-picker-modal.pug @@ -0,0 +1,32 @@ +#compiler-picker-modal.modal.fade.gl_keep(tabindex="-1" role="dialog") + .modal-dialog.modal-lg + .modal-content + .modal-header + h5.modal-title Compilers + button.close(type="button" data-dismiss="modal" aria-hidden="true" aria-label="Close") + span(aria-hidden="true") + | × + .modal-body + .card + .card-body + .container + .row + .col-lg + div.filters Filter: + input.compiler-search(value="" placeholder="🔍 Search for a compiler...") + .row + .col-lg + div.filters Instruction Sets: + span.architectures + .row + .col-lg + div.filters Categories: + span.compiler-types + .row + .compilers-col.col-md + h6 Results + .row.compilers-row + .col-md + .compilers + .col-md + .favorites diff --git a/views/templates/panes/compiler.pug b/views/templates/panes/compiler.pug index 45ec083a6..7824f24d7 100644 --- a/views/templates/panes/compiler.pug +++ b/views/templates/panes/compiler.pug @@ -10,6 +10,10 @@ mixin newPaneButton(classId, text, title, icon) .input-group-prepend .input-group select.compiler-picker + .input-group-append + button.btn.btn-sm.btn-light.input-group-text.picker-popout-button(data-trigger="click" style="cursor: pointer;" role="button" title="Compiler picker popout") + span + i.fa-solid.fa-arrow-up-right-from-square .input-group-append button.btn.btn-sm.btn-light.input-group-text.prepend-options(data-trigger="click" style="cursor: pointer;" role="button" title="All compilation options") span.status-icon diff --git a/views/templates/panes/executor.pug b/views/templates/panes/executor.pug index b1e02a84e..b2a990c04 100644 --- a/views/templates/panes/executor.pug +++ b/views/templates/panes/executor.pug @@ -29,6 +29,10 @@ .input-group-prepend .input-group select.compiler-picker + .input-group-append + button.btn.btn-sm.btn-light.input-group-text.picker-popout-button(data-trigger="click" style="cursor: pointer;" role="button" title="Compiler picker popout") + span + i.fa-solid.fa-arrow-up-right-from-square .input-group-append button.btn.btn-sm.btn-light.input-group-text.prepend-options(data-trigger="click" style="cursor: pointer;" role="button" title="All compilation options") span.btn.btn-sm.btn-light.status-icon diff --git a/views/templates/widgets/compiler-selector.pug b/views/templates/widgets/compiler-selector.pug index e8831b0fb..98879ffdc 100644 --- a/views/templates/widgets/compiler-selector.pug +++ b/views/templates/widgets/compiler-selector.pug @@ -4,6 +4,10 @@ .input-group-prepend .input-group select.compiler-picker + .input-group-append + button.btn.btn-sm.btn-light.input-group-text.picker-popout-button(data-trigger="click" style="cursor: pointer;" role="button" title="Compiler picker popout") + span + i.fa-solid.fa-arrow-up-right-from-square .input-group-append button.btn.btn-sm.btn-light.input-group-text.prepend-options(tabindex="0" data-trigger="focus" style="cursor: pointer;" role="button" title="All compilation options") span.btn.btn-sm.btn-light.status-icon