├── .config └── dotnet-tools.json ├── .github └── workflows │ ├── pr.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── .paket ├── Paket.Restore.targets └── paket.exe ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── TODO.md ├── build.cmd ├── build.sh ├── build ├── Program.fs ├── build.fsproj └── paket.references ├── client ├── extension │ ├── extension.ts │ ├── fileExplorer.ts │ └── graphPanel.ts ├── test │ ├── runTest.ts │ ├── sample mod │ │ ├── common │ │ │ ├── button_effects │ │ │ │ └── irm_button.txt │ │ │ ├── defines │ │ │ │ └── irm_defines.txt │ │ │ ├── edicts │ │ │ │ └── irm_planetary_edicts.txt │ │ │ ├── on_actions │ │ │ │ └── irm_on_actions.txt │ │ │ ├── policies │ │ │ │ └── irm_policies.txt │ │ │ ├── pop_faction_types │ │ │ │ └── irm_regionalist.txt │ │ │ ├── scripted_effects │ │ │ │ ├── irm_helpers.txt │ │ │ │ └── irm_scripted_effects.txt │ │ │ ├── scripted_triggers │ │ │ │ └── irm_scripted_triggers.txt │ │ │ ├── sector_types │ │ │ │ └── irm_sector_types.txt │ │ │ ├── static_modifiers │ │ │ │ └── irm_static_modifiers.txt │ │ │ └── traits │ │ │ │ └── irm_leader_traits.txt │ │ ├── events │ │ │ ├── irm.txt │ │ │ ├── irm_faction.txt │ │ │ └── irm_sector.txt │ │ ├── gfx │ │ │ ├── interface │ │ │ │ ├── buttons │ │ │ │ │ ├── button_faction_communicate.dds │ │ │ │ │ ├── button_faction_develop.dds │ │ │ │ │ ├── button_faction_install.dds │ │ │ │ │ ├── button_sector_dev_upgrade.dds │ │ │ │ │ ├── sector_military.dds │ │ │ │ │ └── sector_military_selected.dds │ │ │ │ ├── icons │ │ │ │ │ ├── custom │ │ │ │ │ │ ├── button_sector_armies.dds │ │ │ │ │ │ ├── button_sector_fleets.dds │ │ │ │ │ │ ├── button_sector_na.dds │ │ │ │ │ │ ├── icon_faction_army.dds │ │ │ │ │ │ ├── icon_faction_fleet.dds │ │ │ │ │ │ └── icon_sector_logo.dds │ │ │ │ │ ├── faction_icons │ │ │ │ │ │ └── faction_icons_regionalist.dds │ │ │ │ │ ├── planet_modifiers │ │ │ │ │ │ └── modifier_frames.dds │ │ │ │ │ └── traits │ │ │ │ │ │ └── leader_traits │ │ │ │ │ │ ├── trait_edict_energy.dds │ │ │ │ │ │ ├── trait_edict_ethics.dds │ │ │ │ │ │ ├── trait_edict_food.dds │ │ │ │ │ │ ├── trait_edict_growth.dds │ │ │ │ │ │ ├── trait_edict_happiness.dds │ │ │ │ │ │ ├── trait_edict_infrastructure.dds │ │ │ │ │ │ ├── trait_edict_migration.dds │ │ │ │ │ │ ├── trait_edict_minerals.dds │ │ │ │ │ │ ├── trait_edict_regionalism.dds │ │ │ │ │ │ ├── trait_edict_research.dds │ │ │ │ │ │ ├── trait_governor_charismatic.dds │ │ │ │ │ │ ├── trait_governor_industrialist.dds │ │ │ │ │ │ ├── trait_governor_investor.dds │ │ │ │ │ │ ├── trait_ruler_administrator.dds │ │ │ │ │ │ ├── trait_ruler_negotiator.dds │ │ │ │ │ │ └── trait_ruler_outerspace.dds │ │ │ │ └── ui │ │ │ │ │ ├── bar_blue.dds │ │ │ │ │ ├── bar_empty.dds │ │ │ │ │ ├── bar_green.dds │ │ │ │ │ ├── bar_grey.dds │ │ │ │ │ ├── bar_red.dds │ │ │ │ │ ├── bar_yellow.dds │ │ │ │ │ ├── faction │ │ │ │ │ ├── button_faction_settings_autonomy.dds │ │ │ │ │ ├── button_faction_settings_bribe.dds │ │ │ │ │ ├── button_faction_settings_dev_capital.dds │ │ │ │ │ ├── button_faction_settings_propose.dds │ │ │ │ │ └── button_faction_settings_spec.dds │ │ │ │ │ ├── sector │ │ │ │ │ ├── button_sector_army_rise.dds │ │ │ │ │ ├── button_sector_fleet_orders.dds │ │ │ │ │ └── button_sector_fleet_rise.dds │ │ │ │ │ └── sep_vertical.dds │ │ │ └── portraits │ │ │ │ └── city_sets │ │ │ │ └── faction_room.dds │ │ ├── interface │ │ │ ├── irm_sector.gui │ │ │ ├── irm_sector_development.gui │ │ │ ├── irm_sector_politics.gui │ │ │ └── zzz_sprites.gfx │ │ └── localisation │ │ │ ├── irm_l_english.yml │ │ │ ├── irm_l_french.yml │ │ │ ├── irm_l_german.yml │ │ │ ├── irm_l_polish.yml │ │ │ ├── irm_l_russian.yml │ │ │ └── irm_l_spanish.yml │ └── suite │ │ ├── extension.test.ts │ │ └── index.ts ├── types.d.ts └── webview │ ├── canvas.ts │ ├── cytoscape-qtip.d.ts │ ├── eventsgraph.ts │ ├── graph.ts │ └── site.css ├── fsharp-language-server.sln ├── fsharp-language-server.sln.DotSettings.user ├── global.json ├── package.json ├── paket.dependencies ├── paket.lock ├── release ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── docs │ ├── completion.gif │ ├── cwtools logo purp.xcf │ ├── cwtools logo.xcf │ ├── cwtools_logo.png │ ├── findallrefs.png │ ├── gotodef.gif │ ├── localisationerror.gif │ ├── scopeerror.gif │ ├── scopetooltip.gif │ └── tooltips.gif ├── language-configuration.json ├── package-lock.json ├── package.json ├── snippets │ └── paradox.json └── syntaxes │ ├── paradox.tmLanguage.json │ └── stellaris.tmLanguage.json ├── rollup.config.mjs ├── src ├── .vscode │ └── launch.json ├── LSP │ ├── DocumentStore.fs │ ├── LSP.fsproj │ ├── LanguageServer.fs │ ├── Log.fs │ ├── Parser.fs │ ├── Ser.fs │ ├── Tokenizer.fs │ ├── Types.fs │ ├── paket.exe │ └── paket.references └── Main │ ├── Completion.fs │ ├── GameLoader.fs │ ├── Git.fs │ ├── LanguageServerFeatures.fs │ ├── Main.Linux.fsproj │ ├── Main.Linux.fsproj.paket.references │ ├── Main.fsproj │ ├── Main.fsproj.paket.references │ ├── Program.fs │ ├── ProjectManager.fs │ ├── Serialize.fs │ └── rootDescriptor.xml ├── tsconfig.extension.json ├── tsconfig.json └── tsconfig.webview.json /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "8.0.0", 7 | "commands": [ 8 | "paket" 9 | ] 10 | }, 11 | "fake-cli": { 12 | "version": "5.20.0", 13 | "commands": [ 14 | "fake" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - 'master' 5 | pull_request: 6 | branches: 7 | - '*' 8 | 9 | name: Test 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: 8.0.x 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: 18 24 | - run: npm install 25 | - name: Set chmod 26 | run: chmod +x ./build.sh 27 | - name: Test 28 | run: /bin/sh ./build.sh DryRelease 29 | - name: 'Upload Artifact' 30 | uses: actions/upload-artifact@v3 31 | with: 32 | name: cwtools-vscode-pipeline.vsix 33 | path: temp/*.vsix 34 | retention-days: 5 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | username: 5 | description: Github username of the person triggering the release 6 | default: "tboby" 7 | required: true 8 | email: 9 | description: Email of the person triggering the release 10 | default: "th.boby@gmail.com" 11 | required: true 12 | 13 | 14 | name: Release 15 | 16 | jobs: 17 | release: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | ref: ${{ github.head_ref }} 24 | - name: Setup .NET Core 25 | uses: actions/setup-dotnet@v3 26 | with: 27 | dotnet-version: 8.0.x 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 18 31 | # cache: 'npm' 32 | - run: npm install 33 | - name: Set chmod 34 | run: chmod +x ./build.sh 35 | - name: Test 36 | run: /bin/sh ./build.sh DryRelease 37 | - name: upload artifact package 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: cwtools-fsharp-vscode-ext 41 | path: release/ 42 | - name: Publish Release 43 | env: 44 | github-user: ${{ github.event.inputs.username }} 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | vsce-token: ${{ secrets.VSCE_TOKEN }} 47 | user-email: ${{ github.event.inputs.email }} 48 | run: dotnet run --project build -- -t Release 49 | - name: Publish to Open VSX Registry 50 | uses: HaaLeo/publish-vscode-extension@v1 51 | id: publishToOpenVSX 52 | with: 53 | packagePath: "./release" 54 | pat: ${{ secrets.OPEN_VSX_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | out/ 4 | packages/ 5 | node_modules/ 6 | .idea/ 7 | *.vsix 8 | Scratch.* 9 | paket-files/ 10 | .fake/ 11 | BuildPackages/ 12 | nupkgs/ 13 | .vsce 14 | .cwtools/ 15 | .ionide/ 16 | .vscode-test/ 17 | .fake 18 | .ionide -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/Main/config"] 2 | path = src/Main/config 3 | url = https://github.com/tboby/cwtools-stellaris-config 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.paket/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/.paket/paket.exe -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": ".NET Core Attach", 10 | "type": "coreclr", 11 | "request": "attach", 12 | "processId": "${command:pickProcess}", 13 | "symbolOptions": { 14 | "searchPaths": ["${workspaceFolder}/out/server/local"], 15 | "searchMicrosoftSymbolServer": true 16 | }, 17 | "justMyCode": false 18 | }, 19 | { 20 | "name": "Extension", 21 | "type": "extensionHost", 22 | "request": "launch", 23 | "runtimeExecutable": "${execPath}", 24 | "args": [ 25 | "F:\\Games\\Steam\\steamapps\\common\\Stellaris", "--extensionDevelopmentPath=${workspaceFolder}" 26 | ], 27 | "outFiles": [ 28 | "${workspaceFolder}/out/**/*.js" 29 | ], 30 | "preLaunchTask": "npm: watch" 31 | }, 32 | { 33 | "name": "Extension Tests", 34 | "type": "extensionHost", 35 | "request": "launch", 36 | "runtimeExecutable": "${execPath}", 37 | "args": [ 38 | "${workspaceFolder}/client/test/sample mod", 39 | "${workspaceFolder}/client/test/sample mod/events/irm.txt", 40 | "--extensionDevelopmentPath=${workspaceFolder}", 41 | "--extensionTestsPath=${workspaceFolder}/out/client/test/suite" 42 | ], 43 | "outFiles": [ 44 | "${workspaceFolder}/out/client/test/**/*.js" 45 | ] 46 | }, 47 | { 48 | "preLaunchTask": "Build", 49 | "name": "Quick Build and Launch Extension", 50 | "type": "extensionHost", 51 | "runtimeExecutable": "${execPath}", 52 | "args": ["D:\\Games\\Steam\\steamapps\\common\\Stellaris", "--extensionDevelopmentPath=${workspaceRoot}/release" ], 53 | "stopOnEntry": false, 54 | "request": "launch", 55 | "sourceMaps": false 56 | }, 57 | { 58 | "preLaunchTask": "UpdateBuild", 59 | "name": "Quick update, Build and Launch Extension", 60 | "type": "extensionHost", 61 | "runtimeExecutable": "${execPath}", 62 | "args": [ 63 | "D:\\Games\\Steam\\steamapps\\common\\Stellaris", 64 | "--extensionDevelopmentPath=${workspaceRoot}/release" 65 | ], 66 | "request": "launch", 67 | "sourceMaps": false 68 | }, 69 | { 70 | "preLaunchTask": "UpdateBuildLocal", 71 | "name": "Quick update, Build and Launch Extension (Local)", 72 | "type": "extensionHost", 73 | "runtimeExecutable": "${execPath}", 74 | "args": [ 75 | "D:\\Games\\Steam\\steamapps\\common\\Stellaris", 76 | "--extensionDevelopmentPath=${workspaceRoot}/release" 77 | ], 78 | "request": "launch", 79 | "sourceMaps": false 80 | }, 81 | { 82 | "name": "Launch Only", 83 | "type": "extensionHost", 84 | "runtimeExecutable": "${execPath}", 85 | "args": ["--extensionDevelopmentPath=${workspaceRoot}/release" ], 86 | "stopOnEntry": false, 87 | "request": "launch", 88 | "sourceMaps": false, 89 | 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch-client", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "command": "${workspaceRoot}/build.sh", 21 | "windows": { 22 | "command": "${workspaceRoot}/build.cmd" 23 | }, 24 | "label": "BuildLocal", 25 | "args": [ 26 | "QuickBuildLocal" 27 | ], 28 | "group": { 29 | "kind": "build", 30 | "isDefault": true 31 | } 32 | }, 33 | { 34 | "command": "${workspaceRoot}/build.sh", 35 | "windows": { 36 | "command": "${workspaceRoot}/build.cmd" 37 | }, 38 | "label": "Build", 39 | "args": [ 40 | "QuickBuild" 41 | ], 42 | "group": { 43 | "kind": "build", 44 | "isDefault": true 45 | } 46 | }, 47 | { 48 | "label": "PaketUpdate", 49 | "command": "dotnet", 50 | "args": ["paket", "update", "-g", "git"] 51 | }, 52 | { 53 | "label": "UpdateBuild", 54 | "dependsOn" :[ "PaketUpdate" ,"Build" ], 55 | "dependsOrder": "sequence" 56 | }, 57 | { 58 | "label": "UpdateBuildLocal", 59 | "dependsOn": [ 60 | "PaketUpdate", 61 | "BuildLocal" 62 | ], 63 | "dependsOrder": "sequence" 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Boby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | The contents of the src/LSP directory are derived, Copyright (c) [F# Language Server Contributors](https://github.com/fsprojects/fsharp-language-server/graphs/contributors) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [CWTools: Paradox Language Services](https://marketplace.visualstudio.com/items/tboby.cwtools-vscode) 2 | 3 | **Paradox Language Features for Visual Studio Code** 4 | 5 | ## Disclaimer 6 | 7 | This extension is still in preview, it may not work, it may stop working at any time. 8 | **Make backups of your mod files.** 9 | 10 | ## Supported games 11 | 12 | * Stellaris 13 | * Hearts of Iron IV 14 | * Europa Universalis IV 15 | * Imperator: Rome - outdated, help needed 16 | * Crusader Kings II - partial 17 | * Crusader Kings III - in progress, help needed 18 | * Victoria 3 - in progress, help needed 19 | 20 | ## Features 21 | 22 | * Immediate highlighting of syntax errors 23 | * Autocomplete while you type, providing descriptions when available 24 | * Tooltips on hover showing: 25 | * Related localisation 26 | * Documentation for that element 27 | * Scope context at that position 28 | * A wide range of validators for common, interface, and events, checking 29 | * That required localisation keys are defined 30 | * Existence of effects/triggers/modifiers 31 | * Scope context for used effects/triggers/modifiers 32 | * Usage of scripted effects/triggers 33 | * Correct entries for weights/AI_chance/etc 34 | * That event\_targets are saved before they're used 35 | * That referenced sprites and graphics files exist 36 | * and a number of other specific validators 37 | * "Code actions" to generate .yml for missing localisation 38 | 39 | ### Completion 40 | 41 | ![Completion](./docs/completion.gif) 42 | 43 | ### Tooltips 44 | 45 | ![Tooltips](./docs/tooltips.gif) 46 | 47 | ### Scope tooltips 48 | 49 | ![Scope tooltips](./docs/scopetooltip.gif) 50 | 51 | ### Scope errors 52 | 53 | ![Scope ](./docs/scopeerror.gif) 54 | 55 | ### Localisation error 56 | 57 | ![Localisation error](./docs/localisationerror.gif) 58 | 59 | ### Go to definition 60 | 61 | ![Go to definition](./docs/gotodef.gif) 62 | 63 | ### Find all references 64 | 65 | ![Find all references](./docs/findallrefs.png) 66 | 67 | ## Usage 68 | 69 | 1. Install this extension 70 | 2. If on linux, possibly follow [these instructions](https://code.visualstudio.com/docs/setup/linux#_error-enospc) 71 | 3. If on linux, install libcurl3 72 | 4. Either open your mod folder directly 73 | 5. or open the Game folder containing your mods. E.g. for Stellaris this can be one of: 74 | * "C:\Users\name\Paradox Interactive\Stellaris" 75 | * "C:\Program Files(x86)\Steam\steamapps\common\Stellaris" 76 | 77 | or on linux 78 | * "/home/name/.local/share/Paradox Interactive/Stellars" 79 | * "/home/name/.steam/steam/steamapps/common/Stellaris" 80 | 6. Follow the prompts to select your vanilla folder 81 | 7. Edit files and watch syntax errors show up when you make mistakes 82 | 8. Wait up to a minute for the extension to scan all your mods and find all errors 83 | 84 | ## Links 85 | 86 | * [vic2-config](https://github.com/cwtools/cwtools-vic2-config) 87 | * [vic3-config](https://github.com/cwtools/cwtools-vic3-config) 88 | * [ck2-config](https://github.com/cwtools/cwtools-ck2-config) 89 | * [eu4-config](https://github.com/cwtools/cwtools-eu4-config) 90 | * [hoi4-config](https://github.com/cwtools/cwtools-hoi4-config) 91 | * [stellaris-config](https://github.com/cwtools/cwtools-stellaris-config) 92 | * [ir-config](https://github.com/cwtools/cwtools-ir-config) 93 | * [ck3-config](https://github.com/cwtools/cwtools-ck3-config) 94 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Highlighting 2 | - UnionConstructor { ... } is highlighted like seq { ... } 3 | - ``some-name`` is highlighted wrong at - 4 | - ``name`` is highlighted like a function name regardless of context 5 | - "string format %d" doesn't format %d 6 | - @"\w" doesn't highlight regexes 7 | - Missing keywords: 8 | - to 9 | - downto 10 | - type 11 | - not 12 | - done 13 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | dotnet tool restore 5 | dotnet paket restore 6 | if errorlevel 1 ( 7 | exit /b %errorlevel% 8 | ) 9 | 10 | dotnet run --project build -- -t %* 11 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dotnet tool restore 4 | dotnet paket restore 5 | exit_code=$? 6 | if [ $exit_code -ne 0 ]; then 7 | exit $exit_code 8 | fi 9 | dotnet run --project build -- -t $@ -------------------------------------------------------------------------------- /build/build.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/paket.references: -------------------------------------------------------------------------------- 1 | group build 2 | 3 | Fake.Core 4 | Fake.Core.Target 5 | Fake.IO.FileSystem 6 | Fake.DotNet.Cli 7 | Fake.DotNet.Paket 8 | Fake.JavaScript.Npm 9 | Fake.Core.UserInput 10 | Fake.Core.ReleaseNotes 11 | Fake.Tools.Git 12 | Fake.Api.GitHub 13 | FSharp.Collections.ParallelSeq -------------------------------------------------------------------------------- /client/extension/fileExplorer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | //#region Utilities 6 | 7 | export interface TreeNode { 8 | isDirectory: Boolean 9 | children: TreeNode[] 10 | fileName: string 11 | uri: string 12 | } 13 | export interface File { 14 | fileName: string 15 | } 16 | export interface FileListItem { 17 | scope: string; 18 | uri: string; 19 | logicalpath: string 20 | } 21 | 22 | export type fileToTreeNodeType = (files: FileListItem[]) => TreeNode[] 23 | 24 | function filesToTreeNodes(arr : FileListItem[]) : TreeNode[] { 25 | var tree : any = {} 26 | function addnode(obj : FileListItem) { 27 | var path = obj.scope + "/" + obj.logicalpath 28 | var splitpath = path.replace(/^\/|\/$/g, "").split('/'); 29 | var ptr = tree; 30 | for (let i = 0; i < splitpath.length; i++) { 31 | let node: any = { 32 | fileName: splitpath[i], 33 | isDirectory: true, 34 | }; 35 | if (i == splitpath.length - 1) { 36 | node.uri = obj.uri; 37 | node.isDirectory = false; 38 | // console.log(splitpath[i] + "," + obj.fileName) 39 | } 40 | ptr[splitpath[i]] = ptr[splitpath[i]] || node; 41 | ptr[splitpath[i]].children = ptr[splitpath[i]].children || {}; 42 | ptr = ptr[splitpath[i]].children; 43 | } 44 | } 45 | function objectToArr(node : any) { 46 | Object.keys(node || {}).map((k) => { 47 | if (node[k].children) { 48 | objectToArr(node[k]) 49 | } 50 | }) 51 | if (node.children) { 52 | node.children = (Object).values(node.children) 53 | node.children.forEach(objectToArr) 54 | } 55 | } 56 | arr.map(addnode); 57 | objectToArr(tree) 58 | return (Object).values(tree) 59 | } 60 | 61 | // interface Entry { 62 | // uri: vscode.Uri; 63 | // type: vscode.FileType; 64 | // } 65 | 66 | export class FilesProvider implements vscode.TreeDataProvider { 67 | private tree : TreeNode; 68 | constructor(private files: FileListItem[]) { 69 | // const t : File[] = files.map(f => ({fileName: f})); 70 | // this.tree = { fileName: "root", isDirectory: true, children: filesToTreeNodes(files), uri: "" }; 71 | this.parseTree(files); 72 | } 73 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 74 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 75 | 76 | 77 | private parseTree(files: FileListItem[]): void { 78 | this.tree = { 79 | fileName: "root", 80 | isDirectory: true, 81 | children: filesToTreeNodes(files), 82 | uri: "" 83 | }; 84 | } 85 | 86 | getTreeItem(element: TreeNode): vscode.TreeItem { 87 | const treeItem = new vscode.TreeItem(element.fileName, element.isDirectory ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); 88 | if (!element.isDirectory) { 89 | treeItem.command = { command: 'cwtools-files.openFile', title: "Open File", arguments: [vscode.Uri.parse(element.uri)], }; 90 | treeItem.contextValue = 'file'; 91 | treeItem.resourceUri = vscode.Uri.parse(element.uri) 92 | } 93 | return treeItem; 94 | } 95 | async getChildren(element?: TreeNode): Promise { 96 | if (element) { 97 | return element.children; 98 | // const children = await this.readDirectory(element.uri); 99 | // return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(element.uri.fsPath, name)), type })); 100 | } 101 | else { 102 | return this.tree.children; 103 | } 104 | 105 | // const workspaceFolder = vscode.workspace.workspaceFolders.filter(folder => folder.uri.scheme === 'file')[0]; 106 | // if (workspaceFolder) { 107 | // const children = await this.readDirectory(workspaceFolder.uri); 108 | // children.sort((a, b) => { 109 | // if (a[1] === b[1]) { 110 | // return a[0].localeCompare(b[0]); 111 | // } 112 | // return a[1] === vscode.FileType.Directory ? -1 : 1; 113 | // }); 114 | // return children.map(([name, type]) => ({ uri: vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, name)), type })); 115 | // } 116 | 117 | } 118 | refresh(files : FileListItem[]) { 119 | this.parseTree(files); 120 | this._onDidChangeTreeData.fire(undefined); 121 | } 122 | 123 | } 124 | 125 | export class FileExplorer { 126 | 127 | private fileExplorer: vscode.TreeView; 128 | private treeDataProvider: FilesProvider; 129 | 130 | constructor(context: vscode.ExtensionContext, files : FileListItem[]) { 131 | this.treeDataProvider = new FilesProvider(files); 132 | this.fileExplorer = vscode.window.createTreeView('cwtools-files', { treeDataProvider: this.treeDataProvider }); 133 | context.subscriptions.push(vscode.commands.registerCommand('cwtools-files.openFile', (resource) => this.openResource(resource))); 134 | } 135 | 136 | private openResource(resource: vscode.Uri): void { 137 | vscode.window.showTextDocument(resource); 138 | } 139 | 140 | refresh(files : FileListItem[]): void { 141 | this.treeDataProvider.refresh(files); 142 | // this.fileExplorer.dispose(); 143 | // const treeDataProvider = new FilesProvider(files); 144 | // this.fileExplorer = vscode.window.createTreeView('cwtools-files', { treeDataProvider }); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /client/extension/graphPanel.ts: -------------------------------------------------------------------------------- 1 | import vscode from "vscode"; 2 | import * as path from 'path'; 3 | import * as fs from 'fs' 4 | import { EIO } from "constants"; 5 | import { isString } from "util"; 6 | import rpc from "vscode-jsonrpc"; 7 | 8 | 'use strict'; 9 | 10 | enum State { 11 | New, 12 | DataReady, 13 | ClientReady, 14 | Done 15 | } 16 | export class GraphPanel { 17 | 18 | /** 19 | * Track the currently panel. Only allow a single panel to exist at a time. 20 | */ 21 | public static currentPanel: GraphPanel | undefined; 22 | private static readonly viewType = 'cwtools-graph'; 23 | private readonly _panel: vscode.WebviewPanel; 24 | private _state: State; 25 | private readonly _onLoad = new vscode.EventEmitter(); 26 | private readonly onLoad: vscode.Event = this._onLoad.event; 27 | 28 | private _disposables: vscode.Disposable[] = []; 29 | private readonly _webviewRootPath: string; 30 | 31 | 32 | public static create(extensionPath: string) { 33 | const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; 34 | 35 | // If we already have a panel, dispose of it. 36 | // Create a new panel. 37 | if (GraphPanel.currentPanel && GraphPanel.currentPanel._state != State.New && GraphPanel.currentPanel._state != State.ClientReady) { 38 | GraphPanel.currentPanel.dispose(); 39 | } 40 | if (!GraphPanel.currentPanel){ 41 | GraphPanel.currentPanel = new GraphPanel(extensionPath, column || vscode.ViewColumn.One); 42 | } 43 | } 44 | 45 | private constructor(extensionPath: string, column: vscode.ViewColumn) { 46 | this._webviewRootPath = path.join(extensionPath, 'bin/client/webview'); 47 | 48 | this._state = State.New; 49 | 50 | // Create and show a new webview panel 51 | this._panel = vscode.window.createWebviewPanel(GraphPanel.viewType, "Graph", column, { 52 | // Enable javascript in the webview 53 | enableScripts: true, 54 | retainContextWhenHidden: true, 55 | 56 | // And restric the webview to only loading content from our extension's `media` directory. 57 | localResourceRoots: [ 58 | vscode.Uri.file(this._webviewRootPath) 59 | ] 60 | }); 61 | 62 | // Set the webview's initial html content 63 | this._panel.webview.html = this._getHtmlForWebview(); 64 | 65 | // Listen for when the panel is disposed 66 | // This happens when the user closes the panel or when the panel is closed programatically 67 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 68 | 69 | // Handle messages from the webview 70 | this._disposables.push((this._panel.webview.onDidReceiveMessage(message => { 71 | switch (message.command) { 72 | case 'goToFile': 73 | let uri = vscode.Uri.file(message.uri); 74 | let range = new vscode.Range(message.line, message.column, message.line, message.column); 75 | vscode.window.showTextDocument(uri).then((texteditor) => texteditor.revealRange(range, vscode.TextEditorRevealType.AtTop)) 76 | return; 77 | case 'saveImage': 78 | let image = message.image; 79 | vscode.window.showSaveDialog({ filters: { 'Image': ['png'] } }) 80 | .then((dest) => fs.writeFile(dest.fsPath, image, "base64", (error) => console.error(error))) 81 | return; 82 | case 'saveJson': 83 | let json = message.json; 84 | vscode.window.showSaveDialog({ filters: { 'Json': ['json'] } }) 85 | .then((dest) => fs.writeFile(dest.fsPath, json, "utf-8", (error) => console.error(error))) 86 | return; 87 | case 'ready': 88 | if (this._state == State.DataReady){ 89 | this._onLoad.fire(undefined); 90 | } else { 91 | this._state = State.ClientReady; 92 | } 93 | } 94 | }, null, this._disposables))); 95 | 96 | // Handle state change 97 | this._disposables.push((this._panel.onDidChangeViewState((e) => { 98 | vscode.commands.executeCommand('setContext', "cwtoolsWebview", e.webviewPanel.active); 99 | }, null, this._disposables))) 100 | 101 | // Set up commands 102 | this._disposables.push(vscode.commands.registerCommand('saveGraphImage', () => { 103 | this._panel.webview.postMessage({ "command": "exportImage" }) 104 | })) 105 | this._disposables.push(vscode.commands.registerCommand('saveGraphJson', () => { 106 | this._panel.webview.postMessage({ "command": "exportJson" }) 107 | })) 108 | 109 | vscode.commands.executeCommand('setContext', "cwtoolsWebview", true); 110 | 111 | } 112 | 113 | public initialiseGraph(data : string | Array, wheelSensitivity: number) { 114 | let settings = { 115 | wheelSensitivity: wheelSensitivity 116 | } 117 | if (isString(data)){ 118 | this._disposables.push(this.onLoad((_) => this._panel.webview.postMessage({ "command": "importJson", "json": data, "settings": settings }))); 119 | } else { 120 | this._disposables.push(this.onLoad((_) => this._panel.webview.postMessage({ "command": "go", "data": data, "settings": settings }))); 121 | } 122 | if (this._state == State.Done){ 123 | return; 124 | } 125 | else if (this._state == State.ClientReady){ 126 | this._state = State.Done; 127 | this._onLoad.fire(undefined); 128 | } else { 129 | this._state = State.DataReady; 130 | } 131 | } 132 | 133 | public dispose() { 134 | vscode.commands.executeCommand('setContext', "cwtoolsWebview", false); 135 | 136 | // Clean up our resources 137 | this._panel.dispose(); 138 | this._onLoad.dispose(); 139 | 140 | while (this._disposables.length) { 141 | const x = this._disposables.pop(); 142 | if (x) { 143 | x.dispose(); 144 | } 145 | } 146 | GraphPanel.currentPanel = undefined; 147 | } 148 | 149 | 150 | private _getHtmlForWebview() { 151 | const scriptUri = this._panel.webview.asWebviewUri(vscode.Uri.file(path.join(this._webviewRootPath, 'graph.js'))); 152 | // const scriptPathOnDisk = vscode.Uri.file(path.join(this._webviewRootPath, 'graph.js')); 153 | // const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' }); 154 | // const stylePathOnDisk = vscode.Uri.file(path.join(this._webviewRootPath, 'site.css')); 155 | // const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' }); 156 | const styleUri = this._panel.webview.asWebviewUri(vscode.Uri.file(path.join(this._webviewRootPath, 'site.css'))); 157 | 158 | const nonce = this.getNonce(); 159 | return ` 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
172 | 173 |
174 | 175 |
176 |
177 | 178 | 179 | 180 | 181 | 182 | 183 |
184 | 185 | 186 | `; 187 | 188 | 189 | } 190 | private getNonce() { 191 | let text = ''; 192 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 193 | for (let i = 0; i < 32; i++) { 194 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 195 | } 196 | return text; 197 | }; 198 | } 199 | -------------------------------------------------------------------------------- /client/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | const launchArgs = [ 16 | path.resolve(__dirname, "../../../client/test/sample mod/"), 17 | path.resolve(__dirname, "../../../client/test/sample mod/events/irm.txt"), 18 | "--disable-extensions" 19 | ] 20 | 21 | // Download VS Code, unzip it and run the integration test 22 | await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs }); 23 | } catch (err) { 24 | console.error('Failed to run tests'); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | main(); -------------------------------------------------------------------------------- /client/test/sample mod/common/defines/irm_defines.txt: -------------------------------------------------------------------------------- 1 | NGraphics = { 2 | MAPNAME_SECTOR_SCALE = 5 # sector name size 3 | } 4 | NGameplay = { 5 | POP_FACTION_MIN_POTENTIAL_MEMBERS = 3 # If a faction has less potential members than this, do not form 6 | POP_FACTION_CREATION_COOLDOWN = 30 # Wait this many days after creating a faction to create another one (not applied to hidden factions) 7 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/edicts/irm_planetary_edicts.txt: -------------------------------------------------------------------------------- 1 | # Sets the edict cost and time for all edicts with this variables 2 | @standardEdictCost = 150 3 | @standardEdictLength = 3600 4 | 5 | # Restricted Migration 6 | planet_edict = { 7 | name = "restricted_migration" 8 | influence_cost = @standardEdictCost 9 | length = @standardEdictLength 10 | 11 | modifier = { 12 | planet_migration_all_pull = -9.99 13 | } 14 | 15 | potential = {} 16 | allow = { not = { has_edict = "land_of_opportunity" } } 17 | 18 | ai_weight = { weight = 0 } 19 | } 20 | 21 | 22 | # Establish sector capital 23 | planet_edict = { 24 | name = "sector_capital_new" 25 | influence_cost = 0 26 | length = 0 27 | 28 | potential = { 29 | sector_controlled = yes 30 | sector = { not = { any_planet = { is_sector_capital = yes } } } 31 | } 32 | allow = {} 33 | effect = { 34 | custom_tooltip = "edict_sector_capital_effect" 35 | hidden_effect = { 36 | # Set planet as sector capital 37 | add_modifier = { modifier = "sector_capital" days = -1 } 38 | # Launch sector reconfiguration 39 | # Unassign leader, to prevent disappearing of him 40 | sector = { 41 | if = { limit = { exists = leader } unassign_leader = leader } 42 | sector_do_reconfigure = yes 43 | } 44 | } 45 | } 46 | } 47 | 48 | # Relocate sector capital 49 | planet_edict = { 50 | name = "sector_capital_move" 51 | influence_cost = @standardEdictCost 52 | length = 0 53 | 54 | potential = { 55 | sector_controlled = yes 56 | sector = { any_planet = { is_sector_capital = yes } } 57 | } 58 | allow = { 59 | custom_tooltip = { 60 | fail_text = "edict_sector_capital_not_established" 61 | not = { has_modifier = "sector_capital" } 62 | } 63 | custom_tooltip = { 64 | fail_text = "edict_sector_capital_has_sector_edict" 65 | sector = { 66 | not = { 67 | any_planet = { 68 | is_sector_capital = yes 69 | has_sector_edict_time = yes 70 | } 71 | } 72 | } 73 | } 74 | } 75 | effect = { 76 | custom_tooltip = "edict_sector_capital_effect" 77 | hidden_effect = { 78 | # Set planet as sector capital 79 | add_modifier = { modifier = "sector_capital" days = -1 } 80 | # Launch sector reconfiguration 81 | # Unassign leader, to prevent disappearing of him 82 | sector = { 83 | if = { limit = { exists = leader } unassign_leader = leader } 84 | sector_do_reconfigure = yes 85 | } 86 | } 87 | } 88 | } 89 | 90 | # Manage Sector 91 | planet_edict = { 92 | name = "manage_sector" 93 | influence_cost = 0 94 | length = 0 95 | 96 | potential = { 97 | sector_controlled = yes 98 | has_modifier = "sector_capital" 99 | } 100 | allow = { 101 | custom_tooltip = { 102 | fail_text = "edict_manage_sector_duplicated" 103 | owner = { not = { any_owned_planet = { has_planet_flag = "ui_management_open" } } } 104 | } 105 | } 106 | effect = { 107 | hidden_effect = { 108 | # Run sector management screen from sector capital 109 | planet_event = { id = irm_sector.2 } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/on_actions/irm_on_actions.txt: -------------------------------------------------------------------------------- 1 | # No scope, like on_game_start 2 | on_monthly_pulse = { 3 | events = { 4 | irm.2 5 | } 6 | } 7 | 8 | # No scope, like on_game_start 9 | on_yearly_pulse = { 10 | events = { 11 | irm.3 12 | } 13 | } 14 | 15 | # A colony has been destroyed. Called just before owner and controller is cleared 16 | # Scope = Planet 17 | on_colony_destroyed = { 18 | events = { 19 | irm_sector.105 20 | } 21 | } 22 | 23 | # A planets controller becomes a country not the same as the owner. 24 | # Root = Planet 25 | # From = Planet Owner 26 | # FromFrom = Planet Controller (the one occupying) 27 | on_planet_occupied = { 28 | events = { 29 | irm_sector.105 30 | } 31 | } 32 | 33 | # Initial rebels manage to take control of the planet, happens before the new owner is set, after the war is created. 34 | # This = Rebel Country 35 | # From = Planet 36 | # FromFrom = War 37 | on_rebels_take_planet = { 38 | events = { 39 | irm_sector.1051 40 | } 41 | } 42 | 43 | # Initial rebels manage to take control of the planet, happens after the new owner is set, after the war is created. 44 | # This = Rebel Country 45 | # From = Planet 46 | # FromFrom = War 47 | on_rebels_take_planet_owner_switched = { 48 | events = { 49 | irm_sector.1051 50 | } 51 | } 52 | 53 | #From = Country scope 54 | #This = Planet scope 55 | on_planet_ownerless = { 56 | events = { 57 | irm_sector.105 58 | } 59 | } 60 | 61 | #Fired whenever a new owner is set for a planet, 62 | #be it after a war or through a trade 63 | #From = Country scope (new owner) 64 | #This = Planet scope 65 | on_planet_transfer = { 66 | events = { 67 | irm_sector.105 68 | } 69 | } 70 | 71 | # Executed as a leader has died 72 | # This = Country 73 | # From = Leader 74 | on_leader_death = { 75 | events = { 76 | irm_faction.2 77 | } 78 | } 79 | 80 | # A pop has finished migrating to another planet 81 | # Root = pop 82 | # From = Tile on the new planet 83 | # FromFrom = Tile on the old planet 84 | on_pop_migration_end = { 85 | events = { 86 | #irm.231 87 | } 88 | } 89 | 90 | # A pop has been resettled to another planet 91 | # Root = pop 92 | # From = Tile on the old planet 93 | on_pop_resettled = { 94 | events = { 95 | #irm.232 96 | } 97 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/policies/irm_policies.txt: -------------------------------------------------------------------------------- 1 | administrative_system = { 2 | potential = { 3 | NOT = { has_ethic = "ethic_gestalt_consciousness" } 4 | has_country_flag="adm_enabled" 5 | } 6 | 7 | option = { 8 | name = "federal" 9 | 10 | policy_flags = { 11 | federal_system 12 | } 13 | 14 | valid = { 15 | has_authority = auth_democratic 16 | sectors > 0 17 | } 18 | 19 | modifier = { 20 | #country_core_sector_system_cap = 1 21 | } 22 | 23 | } 24 | 25 | option = { 26 | name = "corporate" 27 | 28 | policy_flags = { 29 | corporate_system 30 | } 31 | 32 | valid = { 33 | has_authority = auth_oligarchic 34 | sectors > 0 35 | } 36 | 37 | } 38 | 39 | option = { 40 | name = "unitary" 41 | 42 | policy_flags = { 43 | unitary_system 44 | } 45 | 46 | } 47 | 48 | option = { 49 | name = "dominion" 50 | 51 | policy_flags = { 52 | dominion_system 53 | } 54 | 55 | valid = { 56 | has_authority = auth_dictatorial 57 | sectors > 0 58 | } 59 | 60 | } 61 | 62 | option = { 63 | name = "empire" 64 | 65 | policy_flags = { 66 | empire_system 67 | } 68 | 69 | valid = { 70 | has_authority = auth_imperial 71 | sectors > 0 72 | } 73 | 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/pop_faction_types/irm_regionalist.txt: -------------------------------------------------------------------------------- 1 | # Regionalist Faction 2 | 3 | regionalist = { 4 | 5 | # This = Pop faction 6 | unique = no 7 | 8 | # This = Country 9 | # Global Conditions 10 | is_potential = { 11 | is_ai = no 12 | sectors > 0 13 | } 14 | 15 | # This = Country 16 | parameters = { 17 | sector = { 18 | type = sector 19 | valid_objects = { 20 | is_core_sector = no 21 | } 22 | } 23 | } 24 | 25 | # This = Pop faction 26 | valid = { 27 | not = { has_pop_faction_flag = "regionalist_dublicated" } # prevent duplicates 28 | parameter:sector = { 29 | debug_scope_type = sector 30 | owner = { is_same_empire = root.owner } 31 | } 32 | } 33 | 34 | # This = Pop 35 | can_join_faction = { 36 | pop_can_politics = yes 37 | planet = { exists = sector } 38 | not = { has_modifier = "pop_suppressed" } 39 | # temporary planets? to keep the issue with taking out planets 40 | } 41 | 42 | # This = Pop 43 | # !!! WARNING !!! 44 | # All of this stuff must be linked with tooltips in irm_button.txt ! 45 | attraction = { 46 | base = 0 47 | 48 | # BASIC 49 | 50 | # Basic faction attraction within sector 51 | modifier = { 52 | add = 100 53 | is_region_pop = yes 54 | } 55 | 56 | # Attraction for regionalist founders, to establish the faction 57 | modifier = { 58 | add = 9999 59 | is_region_pop = yes 60 | has_pop_faction_flag = "regionalist_founder" 61 | } 62 | 63 | # COUNTRY 64 | 65 | # Colonial Viceroys 66 | modifier = { 67 | factor = 0.85 68 | is_region_pop = yes 69 | owner = { has_tradition = "tr_domination_colonial_viceroys" } 70 | } 71 | 72 | # FACTION 73 | 74 | # Regionalist faction existance 75 | modifier = { 76 | factor = 1.50 77 | is_region_pop = yes 78 | owner = { 79 | count_pop_factions = { 80 | limit = { 81 | is_pop_faction_type = "regionalist" 82 | check_pop_faction_parameter = { which = sector value = parameter:sector } 83 | } 84 | count > 0 85 | } 86 | } 87 | } 88 | 89 | # Regionalist faction membership 90 | modifier = { 91 | factor = 1.75 92 | is_region_pop = yes 93 | member_of_faction = root 94 | } 95 | 96 | # PLANET 97 | 98 | modifier = { 99 | factor = 3.00 100 | is_region_pop = yes 101 | planet = { has_modifier = "disconnected_from_sector" } 102 | } 103 | 104 | # Distance to capital 105 | modifier = { 106 | factor = 1.15 107 | planet = { 108 | exists = sector 109 | sector = { is_same_value = parameter:sector } 110 | distance = { source = parameter:sector.owner.capital_scope min_distance = 50 max_distance = 150 } 111 | } 112 | } 113 | 114 | # POP 115 | 116 | # Government ethos 117 | modifier = { 118 | factor = 0.95 119 | has_same_ethos = owner #### REWORK !!! 120 | } 121 | 122 | # Unemployed Pop 123 | modifier = { 124 | factor = 1.25 125 | is_region_pop = yes 126 | is_unemployed = yes 127 | } 128 | 129 | # Different species 130 | modifier = { 131 | factor = 1.5 132 | is_region_pop = yes 133 | not = { is_same_species = owner_species } 134 | } 135 | 136 | # SECTOR 137 | 138 | # Governor Seat 139 | modifier = { 140 | factor = 1.20 141 | is_region_pop = yes 142 | planet = { sector = { not = { exists = leader } } } 143 | } 144 | modifier = { 145 | factor = 0.85 146 | is_region_pop = yes 147 | planet = { sector = { exists = leader } } 148 | } 149 | 150 | # Sector Edict (Reduces Regionalism) 151 | modifier = { 152 | factor = 0.80 153 | is_region_pop = yes 154 | planet = { 155 | sector = { 156 | exists = leader 157 | leader = { has_trait = "trait_edict_regionalism" } 158 | } 159 | } 160 | } 161 | 162 | } 163 | 164 | # Leader selection 165 | leader = { 166 | base = 100 167 | 168 | modifier = { 169 | factor = 0 170 | nor = { 171 | has_leader_flag = "faction_leader" 172 | is_event_leader = yes 173 | } 174 | } 175 | } 176 | 177 | # This = Pop faction 178 | on_create = { 179 | # Check all potential factions within this sector. 180 | # If this faction is similar to existing, mark as duplicated. 181 | # Duplicated factions will be no valid and never appear. 182 | if = { 183 | limit = { 184 | parameter:sector = { 185 | any_pop = { 186 | exists = pop_faction 187 | pop_faction = { 188 | is_pop_faction_type = "regionalist" 189 | check_pop_faction_parameter = { which = sector value = prevprev } 190 | not = { is_same_value = root } 191 | } 192 | } 193 | } 194 | } 195 | # Mark the duplicate 196 | set_pop_faction_flag = "regionalist_dublicated" 197 | 198 | # Otherwise, establish the faction 199 | else = { 200 | # Name this faction from the sector scope, to get the proper name 201 | parameter:sector = { root = { set_name = "pft_regionalist_name" } } 202 | 203 | # Establish the faction 204 | faction_set_leader = yes 205 | 206 | # Show the default message 207 | pop_faction_event = { id = factions.1 } 208 | } 209 | } 210 | 211 | } 212 | 213 | # This = Country 214 | on_destroy = { 215 | every_owned_leader = { 216 | limit = { 217 | leader_class = governor 218 | has_leader_flag = "faction_leader" 219 | leader_of_faction = no 220 | } 221 | kill_leader = { show_notification = no } 222 | } 223 | } 224 | 225 | # Govenor 226 | demand = { 227 | title = "demand_governor" 228 | desc = "demand_governor_desc" 229 | unfulfilled_title = "demand_governor_not" 230 | 231 | fulfilled_effect = 2.5 232 | unfulfilled_effect = -10 233 | 234 | potential = { 235 | exists = parameter:sector 236 | exists = root.leader 237 | or = { 238 | parameter:sector = { 239 | exists = leader 240 | leader = { 241 | not = { root.leader = { is_same_value = prev } } 242 | } 243 | } 244 | parameter:sector = { 245 | not = { exists = leader } 246 | } 247 | } 248 | } 249 | 250 | trigger = { 251 | exists = parameter:sector 252 | parameter:sector = { 253 | exists = leader 254 | leader = { 255 | not = { root.leader = { is_same_value = prev } } 256 | } 257 | } 258 | } 259 | } 260 | 261 | # Govenor, faction leader 262 | demand = { 263 | title = "demand_governor_faction" 264 | desc = "demand_governor_faction_desc" 265 | #unfulfilled_title = "demand_governor_not" 266 | 267 | fulfilled_effect = 5 268 | #unfulfilled_effect = -5 269 | 270 | potential = { 271 | exists = parameter:sector 272 | exists = root.leader 273 | parameter:sector = { 274 | exists = leader 275 | leader = { 276 | leader_of_faction = yes 277 | root.leader = { is_same_value = prev } 278 | } 279 | } 280 | } 281 | 282 | trigger = { 283 | exists = parameter:sector 284 | parameter:sector = { 285 | exists = leader 286 | leader = { 287 | leader_of_faction = yes 288 | root.leader = { is_same_value = prev } 289 | } 290 | } 291 | } 292 | } 293 | 294 | # Same majority species governor 295 | demand = { 296 | title = "demand_same_gov" 297 | desc = "demand_same_gov_desc" 298 | unfulfilled_title = "demand_same_gov_not" 299 | 300 | fulfilled_effect = 2.5 301 | unfulfilled_effect = -5 302 | 303 | potential = { 304 | exists = parameter:sector 305 | exists = parameter:sector.leader 306 | owner = { 307 | any_owned_pop = { 308 | is_region_pop = yes 309 | pop_can_politics = yes 310 | not = { is_same_species = parameter:sector.leader } 311 | } 312 | } 313 | } 314 | 315 | trigger = { 316 | exists = parameter:sector 317 | exists = parameter:sector.leader 318 | pop_percentage = { 319 | limit = { 320 | is_region_pop = yes 321 | pop_can_politics = yes 322 | not = { is_same_species = parameter:sector.leader } 323 | } 324 | percentage < 0.50 325 | } 326 | } 327 | } 328 | 329 | # Experienced Leadership 330 | demand = { 331 | title = "demand_xp_gov" 332 | desc = "demand_xp_gov_desc" 333 | unfulfilled_title = "demand_xp_gov" 334 | 335 | fulfilled_effect = 2.5 336 | unfulfilled_effect = 0 337 | 338 | potential = { 339 | exists = parameter:sector 340 | exists = parameter:sector.leader 341 | parameter:sector.leader = { has_skill > 4 } 342 | } 343 | 344 | trigger = { 345 | exists = parameter:sector 346 | exists = parameter:sector.leader 347 | parameter:sector.leader = { has_skill > 4 } 348 | } 349 | } 350 | 351 | # Sector's Statecraft 1 352 | demand = { 353 | title = "demand_sector_statecraft" 354 | desc = "demand_sector_statecraft_desc" 355 | #unfulfilled_title = "" 356 | 357 | fulfilled_effect = 2.5 358 | #unfulfilled_effect = -15 359 | 360 | potential = { 361 | exists = parameter:sector 362 | owner = { sectors > 2 sectors < 10 } 363 | } 364 | 365 | trigger = { 366 | exists = parameter:sector 367 | owner = { sectors > 2 sectors < 10 } 368 | } 369 | } 370 | # Sector's Statecraft 2 371 | demand = { 372 | title = "demand_sector_statecraft" 373 | desc = "demand_sector_statecraft_desc" 374 | #unfulfilled_title = "" 375 | 376 | fulfilled_effect = 5 377 | #unfulfilled_effect = -15 378 | 379 | potential = { 380 | exists = parameter:sector 381 | owner = { sectors > 9 } 382 | } 383 | 384 | trigger = { 385 | exists = parameter:sector 386 | owner = { sectors > 9 } 387 | } 388 | } 389 | 390 | # Sector Capital 391 | demand = { 392 | title = "demand_sector_capital" 393 | desc = "demand_sector_capital_desc" 394 | unfulfilled_title = "demand_sector_capital_no" 395 | 396 | #fulfilled_effect = 0 397 | unfulfilled_effect = -15 398 | 399 | potential = { 400 | exists = parameter:sector 401 | parameter:sector = { 402 | not = { any_planet = { is_sector_capital = yes } } 403 | } 404 | } 405 | 406 | trigger = { 407 | exists = parameter:sector 408 | parameter:sector = { any_planet = { is_sector_capital = yes } } 409 | } 410 | } 411 | 412 | # Lost Worlds 413 | demand = { 414 | #title = "demand_lost_worlds" 415 | desc = "demand_lost_worlds_desc" 416 | unfulfilled_title = "demand_lost_worlds" 417 | 418 | #fulfilled_effect = 0 419 | unfulfilled_effect = -20 420 | 421 | potential = { 422 | exists = parameter:sector 423 | owner = { 424 | any_owned_pop = { 425 | exists = pop_faction 426 | pop_faction = { 427 | is_pop_faction_type = "regionalist" 428 | is_same_value = root 429 | } 430 | planet = { 431 | exists = sector 432 | sector = { not = { is_same_value = parameter:sector } } 433 | } 434 | } 435 | } 436 | } 437 | 438 | trigger = { 439 | exists = parameter:sector 440 | not = { 441 | owner = { 442 | any_owned_pop = { 443 | exists = pop_faction 444 | pop_faction = { 445 | is_pop_faction_type = "regionalist" 446 | is_same_value = root 447 | } 448 | planet = { 449 | exists = sector 450 | sector = { not = { is_same_value = parameter:sector } } 451 | } 452 | } 453 | } 454 | } 455 | } 456 | } 457 | 458 | 459 | # This = Pop faction 460 | # Faction management & actions 461 | actions = { 462 | 463 | # Sector Management Screen 464 | manage_sector = { 465 | 466 | title = "action_manage_sector" 467 | description = "action_manage_sector_desc" 468 | 469 | potential = { 470 | exists = parameter:sector 471 | parameter:sector = { any_planet = { is_sector_capital = yes } } 472 | owner = { not = { any_owned_planet = { has_planet_flag = "ui_management_open" } } } 473 | } 474 | effect = { 475 | parameter:sector = { 476 | random_planet = { 477 | limit = { is_sector_capital = yes } 478 | planet_event = { id = irm_sector.2 } 479 | } 480 | } 481 | } 482 | } 483 | 484 | } 485 | 486 | } 487 | -------------------------------------------------------------------------------- /client/test/sample mod/common/scripted_effects/irm_helpers.txt: -------------------------------------------------------------------------------- 1 | # GENERAL 2 | 3 | 4 | 5 | 6 | # Scopes: Any (Country, Star, Planet, Fleet, Leader etc) 7 | # Gives the random values from 10000 to 999999 8 | get_random_6 = { 9 | 10 | # Reset potential variables 11 | set_variable = { which = "rand_uid" value = 0 } 12 | 13 | # Set values for certain signs 14 | get_random_value = yes 15 | set_variable = { which = "var_1" value = "var_rand" } 16 | multiply_variable = { which = "var_5" value = 1 } 17 | 18 | get_random_value = yes 19 | set_variable = { which = "var_2" value = "var_rand" } 20 | multiply_variable = { which = "var_2" value = 10 } 21 | 22 | get_random_value = yes 23 | set_variable = { which = "var_3" value = "var_rand" } 24 | multiply_variable = { which = "var_3" value = 100 } 25 | 26 | get_random_value = yes 27 | set_variable = { which = "var_4" value = "var_rand" } 28 | multiply_variable = { which = "var_4" value = 1000 } 29 | 30 | get_random_value = yes 31 | set_variable = { which = "var_5" value = "var_rand" } 32 | multiply_variable = { which = "var_5" value = 10000 } 33 | 34 | get_random_value = yes 35 | set_variable = { which = "var_6" value = "var_rand" } 36 | multiply_variable = { which = "var_6" value = 100000 } 37 | 38 | # Assemle the value 39 | change_variable = { which = "rand_uid" value = "var_1" } 40 | change_variable = { which = "rand_uid" value = "var_2" } 41 | change_variable = { which = "rand_uid" value = "var_3" } 42 | change_variable = { which = "rand_uid" value = "var_4" } 43 | change_variable = { which = "rand_uid" value = "var_5" } 44 | change_variable = { which = "rand_uid" value = "var_6" } 45 | 46 | # Assign scope uid 47 | set_variable = { which = "uid" value = "rand_uid" } 48 | 49 | # Unset helper's variables 50 | unset_variables = yes 51 | 52 | } 53 | # Scopes: set_variable scopers 54 | # Gives the random value from 1 to 9 55 | get_random_value = { 56 | random_list = { 57 | 12 = { set_variable = { which = "var_rand" value = 1 } } 58 | 11 = { set_variable = { which = "var_rand" value = 2 } } 59 | 11 = { set_variable = { which = "var_rand" value = 3 } } 60 | 11 = { set_variable = { which = "var_rand" value = 4 } } 61 | 11 = { set_variable = { which = "var_rand" value = 5 } } 62 | 11 = { set_variable = { which = "var_rand" value = 6 } } 63 | 11 = { set_variable = { which = "var_rand" value = 7 } } 64 | 11 = { set_variable = { which = "var_rand" value = 8 } } 65 | 11 = { set_variable = { which = "var_rand" value = 9 } } 66 | } 67 | } 68 | 69 | # Scopes: set_variable scopers 70 | # Unset unnecessary variables 71 | unset_variables = { 72 | set_variable = { which = "var_1" value = 0 } 73 | set_variable = { which = "var_2" value = 0 } 74 | set_variable = { which = "var_3" value = 0 } 75 | set_variable = { which = "var_4" value = 0 } 76 | set_variable = { which = "var_5" value = 0 } 77 | set_variable = { which = "var_6" value = 0 } 78 | set_variable = { which = "var_rand" value = 0 } 79 | set_variable = { which = "rand_uid" value = 0 } 80 | } 81 | -------------------------------------------------------------------------------- /client/test/sample mod/common/sector_types/irm_sector_types.txt: -------------------------------------------------------------------------------- 1 | # production_targets: strategic resources 2 | # ships: ships to build 3 | # ai_weight - used for AI chance ( modifier - weight, will replace the weight value | modifier - factor, will multiply the weight value ) 4 | 5 | # Allows sector to build military ships 6 | military = { 7 | production_targets = { 8 | #minerals 9 | #unity 10 | } 11 | 12 | ships = { 13 | constructor 14 | colonizer 15 | corvette 16 | destroyer 17 | cruiser 18 | battleship 19 | } 20 | 21 | ai_weight = { 22 | weight = 0 23 | } 24 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/static_modifiers/irm_static_modifiers.txt: -------------------------------------------------------------------------------- 1 | # COUNTRY 2 | suppressed_regionalism = { 3 | country_resource_influence_add = -1 4 | } 5 | 6 | # PLANET 7 | 8 | # Sector Capital 9 | sector_capital = { 10 | pop_government_ethic_attraction = 0.50 11 | icon = "gfx/interface/icons/planet_modifiers/capital.dds" 12 | icon_frame = 1 13 | } 14 | 15 | # Former Capital 16 | former_capital = { 17 | pop_happiness = -0.05 18 | icon = "gfx/interface/icons/planet_modifiers/capital.dds" 19 | icon_frame = 3 20 | } 21 | 22 | # POP 23 | 24 | # Faction suppressed pop 25 | pop_suppressed = { 26 | pop_happiness = -0.20 27 | pop_government_ethic_attraction = 1.00 28 | } -------------------------------------------------------------------------------- /client/test/sample mod/common/traits/irm_leader_traits.txt: -------------------------------------------------------------------------------- 1 | # Leader traits 2 | 3 | 4 | # RULER 5 | 6 | # Negotiator, increasing trade attractiveness 7 | trait_ruler_negotiator = { 8 | cost = 1 9 | icon = "gfx/interface/icons/traits/leader_traits/trait_ruler_negotiator.dds" 10 | 11 | modification = no 12 | leader_trait = yes 13 | leader_class = { ruler } 14 | 15 | leader_potential_add = { 16 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 17 | } 18 | 19 | modifier = { 20 | country_trade_attractiveness = 0.15 21 | } 22 | 23 | } 24 | 25 | # Administrator, +1 core system 26 | trait_ruler_administrator = { 27 | cost = 1 28 | icon = "gfx/interface/icons/traits/leader_traits/trait_ruler_administrator.dds" 29 | 30 | modification = no 31 | leader_trait = yes 32 | leader_class = { ruler } 33 | 34 | leader_potential_add = { 35 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 36 | } 37 | 38 | modifier = { 39 | country_core_sector_system_cap = 1 40 | } 41 | 42 | } 43 | 44 | # Outer Space, decrease distance effect for colonization 45 | trait_ruler_outerspace = { 46 | cost = 1 47 | icon = "gfx/interface/icons/traits/leader_traits/trait_ruler_outerspace.dds" 48 | 49 | modification = no 50 | leader_trait = yes 51 | leader_class = { ruler } 52 | 53 | leader_potential_add = { 54 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 55 | } 56 | 57 | modifier = { 58 | country_distant_colony_influence_cost_mult = -0.15 59 | } 60 | 61 | } 62 | 63 | 64 | # GOVERNOR (Basic) 65 | 66 | # Industrialist, boosts minerals output 67 | trait_governor_industrialist = { 68 | cost = 1 69 | icon = "gfx/interface/icons/traits/leader_traits/trait_governor_industrialist.dds" 70 | 71 | modification = no 72 | leader_trait = yes 73 | leader_class = { governor } 74 | 75 | leader_potential_add = { 76 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 77 | } 78 | 79 | modifier = { 80 | tile_resource_minerals_mult = 0.1 81 | } 82 | 83 | } 84 | 85 | # Investor, boosts energy output 86 | trait_governor_investor = { 87 | cost = 1 88 | icon = "gfx/interface/icons/traits/leader_traits/trait_governor_investor.dds" 89 | 90 | modification = no 91 | leader_trait = yes 92 | leader_class = { governor } 93 | 94 | leader_potential_add = { 95 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 96 | } 97 | 98 | modifier = { 99 | tile_resource_energy_mult = 0.1 100 | } 101 | } 102 | 103 | # Investor, boosts energy output 104 | trait_governor_charismatic = { 105 | cost = 1 106 | icon = "gfx/interface/icons/traits/leader_traits/trait_governor_charismatic.dds" 107 | 108 | modification = no 109 | leader_trait = yes 110 | leader_class = { governor } 111 | 112 | leader_potential_add = { 113 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 114 | } 115 | 116 | modifier = { 117 | edict_influence_cost = -0.2 118 | edict_length_mult = 0.25 119 | } 120 | } 121 | 122 | 123 | # SECTOR EDICTS 124 | 125 | # Edict - food 126 | trait_edict_food = { 127 | cost = 1 128 | modification = no 129 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_food.dds" 130 | modifier = { 131 | tile_resource_food_mult = 0.10 132 | } 133 | leader_potential_add = { 134 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 135 | } 136 | leader_trait = yes 137 | leader_class = { governor } 138 | initial = no 139 | randomized = no 140 | } 141 | # Edict - energy 142 | trait_edict_energy = { 143 | cost = 1 144 | modification = no 145 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_energy.dds" 146 | modifier = { 147 | tile_resource_energy_mult = 0.10 148 | } 149 | leader_potential_add = { 150 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 151 | } 152 | leader_trait = yes 153 | leader_class = { governor } 154 | initial = no 155 | randomized = no 156 | } 157 | # Edict - minerals 158 | trait_edict_minerals = { 159 | cost = 1 160 | modification = no 161 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_minerals.dds" 162 | modifier = { 163 | tile_resource_minerals_mult = 0.10 164 | } 165 | leader_potential_add = { 166 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 167 | } 168 | leader_trait = yes 169 | leader_class = { governor } 170 | initial = no 171 | randomized = no 172 | } 173 | # Edict - research 174 | trait_edict_research = { 175 | cost = 1 176 | modification = no 177 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_research.dds" 178 | modifier = { 179 | tile_resource_physics_research_mult = 0.10 180 | tile_resource_society_research_mult = 0.10 181 | tile_resource_engineering_research_mult = 0.10 182 | } 183 | leader_potential_add = { 184 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 185 | } 186 | leader_trait = yes 187 | leader_class = { governor } 188 | initial = no 189 | randomized = no 190 | } 191 | # Edict - infrastructure 192 | trait_edict_infrastructure = { 193 | cost = 1 194 | modification = no 195 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_infrastructure.dds" 196 | modifier = { 197 | planet_clear_blocker_cost_mult = -0.10 198 | planet_building_cost_mult = -0.10 199 | } 200 | leader_potential_add = { 201 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 202 | } 203 | leader_trait = yes 204 | leader_class = { governor } 205 | initial = no 206 | randomized = no 207 | } 208 | # Edict - happiness 209 | trait_edict_happiness = { 210 | cost = 1 211 | modification = no 212 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_happiness.dds" 213 | modifier = { 214 | pop_happiness = 0.10 215 | } 216 | leader_potential_add = { 217 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 218 | } 219 | leader_trait = yes 220 | leader_class = { governor } 221 | initial = no 222 | randomized = no 223 | } 224 | # Edict - migration 225 | trait_edict_migration = { 226 | cost = 1 227 | modification = no 228 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_migration.dds" 229 | modifier = { 230 | planet_migration_all_pull = 0.5 231 | } 232 | leader_potential_add = { 233 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 234 | } 235 | leader_trait = yes 236 | leader_class = { governor } 237 | initial = no 238 | randomized = no 239 | } 240 | # Edict - ethics 241 | trait_edict_ethics = { 242 | cost = 1 243 | modification = no 244 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_ethics.dds" 245 | modifier = { 246 | pop_government_ethic_attraction = 0.15 247 | } 248 | leader_potential_add = { 249 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 250 | } 251 | leader_trait = yes 252 | leader_class = { governor } 253 | initial = no 254 | randomized = no 255 | } 256 | # Edict - growth 257 | trait_edict_growth = { 258 | cost = 1 259 | modification = no 260 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_growth.dds" 261 | modifier = { 262 | pop_growth_speed = 0.10 263 | } 264 | leader_potential_add = { 265 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 266 | } 267 | leader_trait = yes 268 | leader_class = { governor } 269 | initial = no 270 | randomized = no 271 | } 272 | # Edict - regionalism 273 | trait_edict_regionalism = { 274 | cost = 1 275 | modification = no 276 | icon = "gfx/interface/icons/traits/leader_traits/trait_edict_ethics.dds" 277 | modifier = { 278 | 279 | } 280 | leader_potential_add = { 281 | NOT = { from = { has_ethic = "ethic_gestalt_consciousness" } } 282 | } 283 | leader_trait = yes 284 | leader_class = { governor } 285 | initial = no 286 | randomized = no 287 | } -------------------------------------------------------------------------------- /client/test/sample mod/events/irm.txt: -------------------------------------------------------------------------------- 1 | # IRM 2 | # Imperial Routine - General Events 3 | 4 | namespace = irm 5 | 6 | # Scope = N/A 7 | # Country events 8 | country_event = { 9 | id = irm.1 10 | hide_window = yes 11 | 12 | trigger = { 13 | is_ai = no 14 | is_country_type = default 15 | } 16 | 17 | immediate = { 18 | # Regular events 19 | country_event = { id = irm_sector.101 } # listener: leader's edict traits 20 | country_event = { id = irm_sector.102 } # listener: update timer for edict's length 21 | country_event = { id = irm_sector.104 } 22 | # listener: update timer for policy cd 23 | } 24 | } 25 | 26 | # Scope = N/A 27 | # Monthly regular events 28 | event = { 29 | id = irm.2 30 | hide_window = yes 31 | is_triggered_only = yes 32 | 33 | immediate = { 34 | every_country = { 35 | limit = { is_ai = no is_country_type = default } 36 | country_event = { id = irm_sector.103 } # sector regular estimations 37 | country_event = { id = irm_sector.201 } # clean up all vars for former sector capitals 38 | country_event = { id = irm_sector.202 } # clean up duplicated capitals 39 | country_event = { id = irm_faction.201 } # some xp for faction leaders 40 | country_event = { id = irm_faction.203 } # refresh faction name 41 | } 42 | } 43 | } 44 | 45 | # Scope = N/A 46 | # Yearly regular events 47 | event = { 48 | id = irm.3 49 | hide_window = yes 50 | is_triggered_only = yes 51 | 52 | immediate = { 53 | every_country = { 54 | limit = { is_ai = no is_country_type = default } 55 | country_event = { id = irm_faction.202 } # faction suppressions 56 | } 57 | } 58 | } 59 | 60 | 61 | 62 | 63 | # TMP 64 | 65 | planet_event = { 66 | id = irm.100 67 | hide_window = yes 68 | is_triggered_only = yes 69 | 70 | immediate = { 71 | owner = { 72 | random_pop_faction = { 73 | limit = { 74 | is_pop_faction_type = "regionalist" 75 | check_pop_faction_parameter = { which = sector value = root.sector } 76 | } 77 | leader = { 78 | kill_leader = { show_notification = yes } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /client/test/sample mod/events/irm_faction.txt: -------------------------------------------------------------------------------- 1 | namespace = irm_faction 2 | 3 | # 000 TRIGGERED ACTIONS 4 | 5 | # This = Country 6 | # From = Leader (dead) 7 | # Tracks faction leader's death, create a new leader for faction 8 | country_event = { 9 | id = irm_faction.2 10 | hide_window = yes 11 | is_triggered_only = yes 12 | 13 | trigger = { 14 | from = { 15 | leader_class = governor 16 | leader_of_faction = yes 17 | has_leader_flag = "faction_leader" 18 | exists = pop_faction 19 | } 20 | } 21 | 22 | # Create new faction leader 23 | immediate = { 24 | from = { 25 | pop_faction = { 26 | faction_set_leader = yes 27 | } 28 | } 29 | } 30 | 31 | } 32 | 33 | # 200 REGULAR ACTIONS 34 | 35 | # This = Country 36 | # Adds small amount of xp for all faction leaders 37 | country_event = { 38 | id = irm_faction.201 39 | hide_window = yes 40 | is_triggered_only = yes 41 | 42 | immediate = { 43 | every_pop_faction = { 44 | limit = { 45 | exists = owner 46 | owner = { is_same_empire = root } 47 | exists = leader 48 | } 49 | leader = { 50 | if = { 51 | # Add xp through variables for faction leaders 52 | # (they cannot receive any xp cuz they are not "direcly owned") 53 | limit = { has_leader_flag = "faction_leader" } 54 | change_variable = { which = num_leader_xp value = 1 } 55 | if = { limit = { check_variable = { which = num_leader_xp value = 200 } } add_skill = 1 } 56 | if = { limit = { check_variable = { which = num_leader_xp value = 300 } } add_skill = 1 } 57 | if = { limit = { check_variable = { which = num_leader_xp value = 825 } } add_skill = 1 } 58 | if = { limit = { check_variable = { which = num_leader_xp value = 1250 } } add_skill = 1 } 59 | if = { limit = { check_variable = { which = num_leader_xp value = 1750 } } add_skill = 1 } 60 | if = { limit = { check_variable = { which = num_leader_xp value = 2325 } } add_skill = 1 } 61 | if = { limit = { check_variable = { which = num_leader_xp value = 2975 } } add_skill = 1 } 62 | if = { limit = { check_variable = { which = num_leader_xp value = 3700 } } add_skill = 1 } 63 | if = { limit = { check_variable = { which = num_leader_xp value = 4500 } } add_skill = 1 } 64 | # For usual leaders just give them xp and clear vars 65 | else = { 66 | add_experience = 1.5 67 | set_variable = { which = num_leader_xp value = 0 } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | # This = Country 76 | # Suppression mechanics: remove a pop from suppressed faction 77 | country_event = { 78 | id = irm_faction.202 79 | hide_window = yes 80 | is_triggered_only = yes 81 | 82 | immediate = { 83 | every_pop_faction = { 84 | limit = { has_modifier = "suppressed_faction" } 85 | random_owned_pop = { 86 | limit = { is_faction_pop = yes planet = { exists = sector sector = { is_core_sector = yes }}} 87 | random = { 88 | chance = 75 89 | # Add modifier 90 | if = { 91 | limit = { not = { has_modifier = "pop_suppressed" } } 92 | add_modifier = { modifier = "pop_suppressed" days = 3600 } 93 | } 94 | # Suppress pop 95 | pop_suppress = yes 96 | } 97 | } } 98 | } 99 | 100 | } 101 | 102 | # This = Country 103 | # Refresh faction name to match it with related sector 104 | country_event = { 105 | id = irm_faction.203 106 | hide_window = yes 107 | is_triggered_only = yes 108 | 109 | immediate = { 110 | every_sector = { 111 | limit = { is_core_sector = no } 112 | owner = { 113 | random_pop_faction = { 114 | limit = { 115 | is_pop_faction_type = "regionalist" 116 | check_pop_faction_parameter = { which = sector value = prevprev } 117 | } 118 | set_name = "pft_regionalist_rename" 119 | } 120 | } 121 | } 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/button_faction_communicate.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/button_faction_communicate.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/button_faction_develop.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/button_faction_develop.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/button_faction_install.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/button_faction_install.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/button_sector_dev_upgrade.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/button_sector_dev_upgrade.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/sector_military.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/sector_military.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/buttons/sector_military_selected.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/buttons/sector_military_selected.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/button_sector_armies.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/button_sector_armies.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/button_sector_fleets.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/button_sector_fleets.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/button_sector_na.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/button_sector_na.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/icon_faction_army.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/icon_faction_army.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/icon_faction_fleet.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/icon_faction_fleet.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/custom/icon_sector_logo.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/custom/icon_sector_logo.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/faction_icons/faction_icons_regionalist.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/faction_icons/faction_icons_regionalist.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/planet_modifiers/modifier_frames.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/planet_modifiers/modifier_frames.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_energy.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_energy.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_ethics.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_ethics.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_food.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_food.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_growth.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_growth.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_happiness.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_happiness.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_infrastructure.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_infrastructure.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_migration.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_migration.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_minerals.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_minerals.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_regionalism.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_regionalism.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_research.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_edict_research.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_charismatic.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_charismatic.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_industrialist.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_industrialist.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_investor.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_governor_investor.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_administrator.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_administrator.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_negotiator.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_negotiator.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_outerspace.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/icons/traits/leader_traits/trait_ruler_outerspace.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_blue.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_blue.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_empty.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_empty.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_green.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_green.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_grey.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_grey.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_red.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_red.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/bar_yellow.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/bar_yellow.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_autonomy.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_autonomy.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_bribe.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_bribe.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_dev_capital.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_dev_capital.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_propose.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_propose.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_spec.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/faction/button_faction_settings_spec.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/sector/button_sector_army_rise.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/sector/button_sector_army_rise.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/sector/button_sector_fleet_orders.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/sector/button_sector_fleet_orders.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/sector/button_sector_fleet_rise.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/sector/button_sector_fleet_rise.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/interface/ui/sep_vertical.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/interface/ui/sep_vertical.dds -------------------------------------------------------------------------------- /client/test/sample mod/gfx/portraits/city_sets/faction_room.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/client/test/sample mod/gfx/portraits/city_sets/faction_room.dds -------------------------------------------------------------------------------- /client/test/sample mod/interface/zzz_sprites.gfx: -------------------------------------------------------------------------------- 1 | spriteTypes = { 2 | 3 | # FACTION 4 | 5 | spriteType = { 6 | name = "GFX_faction_icon_regionalist" 7 | textureFile = "gfx/interface/icons/faction_icons/faction_icons_regionalist.dds" 8 | noOfFrames = 1 9 | } 10 | 11 | #spriteType = { 12 | # name = "GFX_sector_logo" 13 | # texturefile = "gfx/interface/icons/custom/icon_sector_logo.dds" 14 | #} 15 | 16 | # ui icons 17 | 18 | spriteType = { 19 | name = "GFX_sep_v" 20 | texturefile = "gfx/interface/ui/sep_vertical.dds" 21 | } 22 | 23 | # progress bars 24 | 25 | spriteType = { 26 | name = "GFX_bar_empty" 27 | texturefile = "gfx/interface/ui/bar_empty.dds" 28 | } 29 | spriteType = { 30 | name = "GFX_bar_green" 31 | texturefile = "gfx/interface/ui/bar_green.dds" 32 | } 33 | spriteType = { 34 | name = "GFX_bar_yellow" 35 | texturefile = "gfx/interface/ui/bar_yellow.dds" 36 | } 37 | spriteType = { 38 | name = "GFX_bar_red" 39 | texturefile = "gfx/interface/ui/bar_red.dds" 40 | } 41 | spriteType = { 42 | name = "GFX_bar_blue" 43 | texturefile = "gfx/interface/ui/bar_blue.dds" 44 | } 45 | spriteType = { 46 | name = "GFX_bar_grey" 47 | texturefile = "gfx/interface/ui/bar_grey.dds" 48 | } 49 | 50 | # buttons 51 | 52 | spriteType = { 53 | name = "GFX_button_faction_communicate" 54 | texturefile = "gfx/interface/buttons/button_faction_communicate.dds" 55 | } 56 | 57 | spriteType = { 58 | name = "GFX_button_faction_install" 59 | texturefile = "gfx/interface/buttons/button_faction_install.dds" 60 | } 61 | 62 | spriteType = { 63 | name = "GFX_button_faction_develop" 64 | texturefile = "gfx/interface/buttons/button_faction_develop.dds" 65 | } 66 | 67 | # sector military forces 68 | 69 | spriteType = { 70 | name = "GFX_button_sector_fleet_rise" 71 | texturefile = "gfx/interface/ui/sector/button_sector_fleet_rise.dds" 72 | } 73 | spriteType = { 74 | name = "GFX_button_sector_fleet_orders" 75 | texturefile = "gfx/interface/ui/sector/button_sector_fleet_orders.dds" 76 | } 77 | spriteType = { 78 | name = "GFX_button_sector_army_rise" 79 | texturefile = "gfx/interface/ui/sector/button_sector_army_rise.dds" 80 | } 81 | 82 | spriteType = { 83 | name = "GFX_button_sector_dev_upgrade" 84 | texturefile = "gfx/interface/buttons/button_sector_dev_upgrade.dds" 85 | } 86 | 87 | 88 | spriteType = { 89 | name = "GFX_button_settings_bribe" 90 | texturefile = "gfx/interface/ui/faction/button_faction_settings_bribe.dds" 91 | } 92 | 93 | spriteType = { 94 | name = "GFX_button_settings_spec" 95 | texturefile = "gfx/interface/ui/faction/button_faction_settings_spec.dds" 96 | } 97 | 98 | spriteType = { 99 | name = "GFX_button_settings_propose" 100 | texturefile = "gfx/interface/ui/faction/button_faction_settings_propose.dds" 101 | } 102 | 103 | spriteType = { 104 | name = "GFX_button_settings_autonomy" 105 | texturefile = "gfx/interface/ui/faction/button_faction_settings_autonomy.dds" 106 | } 107 | 108 | spriteType = { 109 | name = "GFX_button_settings_dev_capital" 110 | texturefile = "gfx/interface/ui/faction/button_faction_settings_dev_capital.dds" 111 | } 112 | 113 | # MODIFIERS 114 | 115 | spriteType = { 116 | name = "GFX_modifier_frames" 117 | texturefile = "gfx/interface/icons/planet_modifiers/modifier_frames.dds" 118 | noOfFrames = 4 119 | } 120 | 121 | 122 | # SECTOR 123 | 124 | spriteType = { 125 | name = "GFX_sector_military_focus" 126 | texturefile = "gfx/interface/buttons/sector_military.dds" 127 | noOfFrames = 3 128 | } 129 | 130 | spriteType = { 131 | name = "GFX_sector_military_focus_selected" 132 | texturefile = "gfx/interface/buttons/sector_military_selected.dds" 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /client/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import { it, describe, before } from 'mocha'; 13 | //import * as myExtension from '../../extension/extension'; 14 | 15 | // Defines a Mocha test suite to group tests of similar kind together 16 | suite("Extension Tests", () => { 17 | 18 | // Defines a Mocha unit test 19 | test("Something 1", () => { 20 | assert.equal(-1, [1, 2, 3].indexOf(5)); 21 | assert.equal(-1, [1, 2, 3].indexOf(0)); 22 | }); 23 | }); 24 | 25 | before(() => { 26 | 27 | }) 28 | 29 | suite(`Debug Integration Test: `, function() { 30 | test('Extension should be present', () => { 31 | assert.ok(vscode.extensions.getExtension('tboby.cwtools-vscode')); 32 | }); 33 | 34 | test('should activate', function () { 35 | this.timeout(1 * 60 * 1000); 36 | return vscode.extensions.getExtension('tboby.cwtools-vscode').activate().then((_) => { 37 | assert.ok(true); 38 | }); 39 | }); 40 | 41 | describe('should have errors', function () { 42 | this.timeout(1 * 60 * 1000); 43 | it('should have errors', async function (done) { 44 | await vscode.extensions.getExtension('tboby.cwtools-vscode').activate().then((x) => { 45 | setTimeout(() => { 46 | let count = 0; 47 | x.default.diagnostics.forEach(([], [], []) => count++); 48 | assert.ok(count); 49 | done(); 50 | }, 45000); 51 | })}); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /client/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import Mocha from 'mocha'; 3 | import glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } -------------------------------------------------------------------------------- /client/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "executable"; 2 | -------------------------------------------------------------------------------- /client/webview/canvas.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | //import cytoscape from 'cytoscape' 4 | export default function () { 5 | // registers the extension on a cytoscape lib ref 6 | const register = function (cytoscape) { 7 | if (!cytoscape) { 8 | return; 9 | } 10 | 11 | const cyCanvas = function (args) { 12 | const cy = this; 13 | const container = cy.container(); 14 | 15 | const canvas = document.createElement("canvas"); 16 | 17 | container.appendChild(canvas); 18 | 19 | const defaults = { 20 | zIndex: 1, 21 | pixelRatio: "auto", 22 | }; 23 | 24 | const options = Object.assign({}, defaults, args); 25 | 26 | if (options.pixelRatio === "auto") { 27 | options.pixelRatio = window.devicePixelRatio || 1; 28 | } 29 | 30 | function resize() { 31 | const width = container.offsetWidth; 32 | const height = container.offsetHeight; 33 | 34 | const canvasWidth = width * options.pixelRatio; 35 | const canvasHeight = height * options.pixelRatio; 36 | 37 | canvas.width = canvasWidth; 38 | canvas.height = canvasHeight; 39 | 40 | canvas.style.width = `${width}px`; 41 | canvas.style.height = `${height}px`; 42 | 43 | cy.trigger("cyCanvas.resize"); 44 | } 45 | 46 | cy.on("resize", () => { 47 | resize(); 48 | }); 49 | 50 | var styleMap = { 51 | 'position': 'absolute', 52 | 'top': '0', 53 | 'left': '0', 54 | 'z-index': '${options.zIndex}', 55 | }; 56 | 57 | Object.keys(styleMap).forEach((k) => { 58 | canvas.style[k] = styleMap[k]; 59 | }); 60 | 61 | resize(); 62 | 63 | return { 64 | /** 65 | * @return {Canvas} The generated canvas 66 | */ 67 | getCanvas() { 68 | return canvas; 69 | }, 70 | /** 71 | * Helper: Clear the canvas 72 | * @param {CanvasRenderingContext2D} ctx 73 | */ 74 | clear(ctx) { 75 | const width = cy.width(); 76 | const height = cy.height(); 77 | ctx.save(); 78 | ctx.setTransform(1, 0, 0, 1, 0, 0); 79 | ctx.clearRect(0, 0, width * options.pixelRatio, height * options.pixelRatio); 80 | ctx.restore(); 81 | }, 82 | /** 83 | * Helper: Reset the context transform to an identity matrix 84 | * @param {CanvasRenderingContext2D} ctx 85 | */ 86 | resetTransform(ctx) { 87 | ctx.setTransform(1, 0, 0, 1, 0, 0); 88 | }, 89 | /** 90 | * Helper: Set the context transform to match Cystoscape's zoom & pan 91 | * @param {CanvasRenderingContext2D} ctx 92 | */ 93 | setTransform(ctx) { 94 | const pan = cy.pan(); 95 | const zoom = cy.zoom(); 96 | ctx.setTransform(1, 0, 0, 1, 0, 0); 97 | ctx.translate(pan.x * options.pixelRatio, pan.y * options.pixelRatio); 98 | ctx.scale(zoom * options.pixelRatio, zoom * options.pixelRatio); 99 | }, 100 | }; 101 | }; 102 | 103 | cytoscape("core", "cyCanvas", cyCanvas); 104 | }; 105 | 106 | if (typeof module !== "undefined" && module.exports) { 107 | // expose as a commonjs module 108 | module.exports = function (cytoscape) { 109 | register(cytoscape); 110 | }; 111 | } 112 | 113 | if (typeof define !== "undefined" && define.amd) { 114 | // expose as an amd/requirejs module 115 | define("cytoscape-canvas2", () => register); 116 | } 117 | 118 | if (typeof cytoscape !== "undefined") { 119 | // expose to global cytoscape (i.e. window.cytoscape) 120 | register(cytoscape); 121 | } 122 | return register; 123 | } -------------------------------------------------------------------------------- /client/webview/cytoscape-qtip.d.ts: -------------------------------------------------------------------------------- 1 | declare module "cytoscape-qtip"; 2 | declare module "cytoscape-dagre"; 3 | declare module "cytoscape-navigator"; 4 | declare module "cytoscape-canvas"; 5 | declare module "cytoscape-elk"; 6 | declare module "cytoscape-popper"; 7 | declare module "merge-images"; 8 | declare module "tippy.js/esm/index"; -------------------------------------------------------------------------------- /client/webview/eventsgraph.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import dagre from 'dagre' 4 | import cytoscape, { AnimateOptions, CenterOptions, CollectionElements } from 'cytoscape' 5 | import cyqtip from 'cytoscape-qtip' 6 | import cytoscapedagre from 'cytoscape-dagre' 7 | import cytoscapenav from 'cytoscape-navigator' 8 | import cytoscapecanvas from 'cytoscape-canvas' 9 | import handlebars from 'handlebars' 10 | 11 | declare module 'cytoscape' { 12 | interface CollectionElements { 13 | data(arg0: string): string 14 | position(): any 15 | hasClass(arg0: string): any 16 | qtip(qtip: any): any; 17 | } 18 | interface Core { 19 | navigator(options: any): any; 20 | cyCanvas(): any; 21 | } 22 | interface NodeSingular { 23 | popperRef(): Element; 24 | } 25 | 26 | } 27 | 28 | var labelMaxLength = 30; 29 | 30 | 31 | var _data: Array; 32 | var _options: Array; 33 | var _pretty: Array; 34 | 35 | function main(data: Array, triggers: any, options: any, pretties: Array, eventComment: Array, bundleEdges: boolean) { 36 | var localised = new Map(); 37 | var eventComments = new Map(eventComment); 38 | var getLoc = (key: string) => localised.has(key) ? localised.get(key) : key 39 | var getName = (id: string) => eventComments.has(id) ? eventComments.get(id) == "" ? id : eventComments.get(id) : id 40 | _data = data; 41 | _options = options; 42 | _pretty = pretties; 43 | cyqtip(cytoscape, $); 44 | cytoscapedagre(cytoscape, dagre); 45 | cytoscapecanvas(cytoscape); 46 | var nav = cytoscapenav(cytoscape, $); 47 | 48 | var cy = cytoscape({ 49 | container: document.getElementById('cy'), 50 | style: [ // the stylesheet for the graph 51 | { 52 | selector: 'node', 53 | style: { 54 | //'background-color': '#666', 55 | 'label': 'data(label)' 56 | } 57 | }, 58 | 59 | { 60 | selector: 'edge', 61 | style: { 62 | 'width': 3, 63 | 'line-color': '#ccc', 64 | 'mid-target-arrow-color': '#ccc', 65 | 'mid-target-arrow-shape': 'triangle', 66 | 'curve-style': bundleEdges ? 'haystack' : 'bezier', 67 | //'haystack-radius': 0.5 68 | } 69 | } 70 | ], 71 | minZoom: 0.1, 72 | maxZoom: 5, 73 | layout: { 74 | name: 'preset', 75 | padding: 10 76 | } 77 | }) 78 | 79 | var roots = []; 80 | var qtipname = function (text: string) { return { content: text, position: { my: 'top center', at: 'bottom center' }, style: { classes: 'qtip-bootstrap', tip: { width: 16, height: 8 } }, show: { event: 'mouseover' }, hide: { event: 'mouseout' } }; } 81 | 82 | 83 | data.forEach(function (element: any) { 84 | var name; 85 | 86 | name = getName(element.ID); 87 | var desc; 88 | if (element.Desc === '') { 89 | desc = element.ID; 90 | } 91 | else { 92 | desc = getLoc(element.Desc); 93 | } 94 | var node : any = cy.add({ group: 'nodes', data: { id: element.ID, label: name, type: element.Key, hidden: element.Hidden } }); 95 | node.qtip(qtipname(desc)); 96 | }); 97 | 98 | triggers.forEach(function (event: any) { 99 | var parentID = event[0]; 100 | event[1].forEach(function (immediates: any) { 101 | immediates.forEach(function (target: any) { 102 | var childID = target; 103 | cy.add({ group: 'edges', data: { source: parentID, target: childID } }) 104 | 105 | }) 106 | 107 | }) 108 | }); 109 | options.forEach(function (event: any) { 110 | var parentID = event[0]; 111 | event[1].forEach(function (option: any) { 112 | var optionName = option[0][0] + "\n" + option[0][1]; 113 | option[1].forEach(function (target: any) { 114 | if (cy.getElementById(target).length > 0) { 115 | var edge : any = cy.add({ group: 'edges', data: { source: parentID, target: target } }); 116 | if (optionName !== "") { 117 | edge[0].qtip(qtipname(optionName)); 118 | } 119 | } else { 120 | cy.getElementById(parentID).data('deadend_option', true); 121 | } 122 | }) 123 | }) 124 | }) 125 | cy.fit(); 126 | var opts = { name: 'dagre', ranker: 'network-simplex' }; 127 | //var opts = {name:'grid'}; 128 | //var layout = cy.layout(opts); 129 | //var layout = cy.layout({ name: 'dagre', ranker: 'network-simplex' } ); 130 | //layout.run(); 131 | cy.fit(); 132 | 133 | 134 | //layout.run(); 135 | var layer = cy.cyCanvas(); 136 | var canvas = layer.getCanvas(); 137 | var ctx = canvas.getContext('2d'); 138 | 139 | 140 | 141 | function flatten(arr: Array>) { 142 | return arr.reduce(function (flat, toFlatten) { 143 | return flat.concat(toFlatten); 144 | }, []); 145 | } 146 | 147 | 148 | let toProcess = cy.elements(); 149 | var groups: CollectionElements[] = []; 150 | 151 | var t: any = cy.elements(); 152 | groups = t.components(); 153 | var singles = groups.filter((f) => f.length === 1); 154 | var singles2: any = singles.reduce((p, c : any) => p.union(c), cy.collection()) 155 | var rest = groups.filter((f) => f.length !== 1); 156 | 157 | var rest2 = rest.reduce((p, c : any) => p.union(c), cy.collection()) 158 | 159 | var lrest: any = rest2.layout(opts); 160 | lrest.run(); 161 | var bb = rest2.boundingBox({}); 162 | var opts2 = { name: 'grid', condense: true, nodeDimensionsIncludeLabels: true } 163 | var lsingles: any = singles2.layout(opts2); 164 | lsingles.run(); 165 | singles2.shift("y", (singles2.boundingBox({}).y2 + 10) * -1); 166 | cy.fit(); 167 | 168 | cy.on("render", function (evt) { 169 | layer.resetTransform(ctx); 170 | layer.clear(ctx); 171 | 172 | 173 | layer.setTransform(ctx); 174 | 175 | 176 | // Draw shadows under nodes 177 | ctx.shadowColor = "black"; 178 | ctx.shadowBlur = 25 * cy.zoom(); 179 | ctx.fillStyle = "#666"; 180 | cy.nodes().forEach((node) => { 181 | let text: string = node.data('type'); 182 | const eventChars = text.split('_').map(f => f[0].toUpperCase()).join(''); 183 | const eventChar = text[0].toUpperCase(); 184 | const pos = node.position(); 185 | 186 | ctx.fillStyle = node.data('hidden') ? "#EEE" : '#888'; 187 | ctx.beginPath(); 188 | ctx.arc(pos.x, pos.y, 15, 0, 2 * Math.PI, false); 189 | ctx.fill(); 190 | ctx.fillStyle = "black"; 191 | ctx.stroke(); 192 | 193 | if (node.data('deadend_option')) { 194 | ctx.arc(pos.x, pos.y, 13, 0, 2 * Math.PI, false); 195 | ctx.stroke(); 196 | } 197 | 198 | //Set text to black, center it and set font. 199 | ctx.fillStyle = "black"; 200 | ctx.font = "16px sans-serif"; 201 | ctx.textAlign = "center"; 202 | ctx.textBaseline = "middle"; 203 | ctx.fillText(eventChars, pos.x, pos.y); 204 | }); 205 | ctx.restore(); 206 | }); 207 | 208 | 209 | var defaults = { 210 | container: ".cy-row" // can be a HTML or jQuery element or jQuery selector 211 | , viewLiveFramerate: 0 // set false to update graph pan only on drag end; set 0 to do it instantly; set a number (frames per second) to update not more than N times per second 212 | , thumbnailEventFramerate: 30 // max thumbnail's updates per second triggered by graph updates 213 | , thumbnailLiveFramerate: false // max thumbnail's updates per second. Set false to disable 214 | , dblClickDelay: 200 // milliseconds 215 | , removeCustomContainer: true // destroy the container specified by user on plugin destroy 216 | , rerenderDelay: 100 // ms to throttle rerender updates to the panzoom for performance 217 | }; 218 | 219 | //var nav = cy.navigator(defaults); 220 | 221 | cy.on('select', 'node', function (e) { 222 | var node = cy.$('node:selected'); 223 | if (node.nonempty()) { 224 | showDetails(node.data('id')); 225 | } 226 | }); 227 | 228 | 229 | cy.on('select', 'edge', function (e) { 230 | var edges: cytoscape.EdgeCollection = cy.edges('edge:selected'); 231 | var edge: any = edges.first(); 232 | var opts : any = {}; 233 | opts.zoom = cy.zoom(); 234 | opts.center = { eles: edge }; 235 | cy.animate(opts); 236 | }); 237 | 238 | cy.on("resize", function (e) { 239 | $("#cy").width(10); 240 | cy.resize(); 241 | cy.center(); 242 | }); 243 | return cy.jpg({ full: true, output: 'base64' }); 244 | } 245 | 246 | var detailsTemplate = handlebars.compile("

{{title}}

{{desc}}
{{full}}
"); 247 | export function showDetails(id: string) { 248 | var node = _data.filter(x => x.ID === id)[0]; 249 | var pretty = _pretty.filter(x => x[0] === id)[0][1]; 250 | var context = { title: node.ID, desc: node.Desc, full: pretty }; 251 | var html = detailsTemplate(context); 252 | document.getElementById('detailsTarget')!.innerHTML = html; 253 | } 254 | 255 | export function go(filesString: string, bundleEdges: boolean, game: number) { 256 | document.getElementById('detailsTarget')!.innerHTML = "Parsing event file..."; 257 | var files: Array = JSON.parse(filesString); 258 | $.ajax({ 259 | url: "GetData", 260 | data: { "files": JSON.parse(filesString), "game": game }, 261 | contentType: "application/json" 262 | }) 263 | .done(function (data) { 264 | var cy = main(JSON.parse(data.item2), JSON.parse(data.item3), JSON.parse(data.item4), JSON.parse(data.item5), JSON.parse(data.item6), bundleEdges); 265 | if (data.item1 === true) { 266 | document.getElementById('detailsTarget')!.innerHTML = "Click an event to see details"; 267 | } 268 | else { 269 | document.getElementById('detailsTarget')!.innerHTML = "Failed to parse file with error(s)
" + JSON.parse(data.item7) 270 | } 271 | var base64 = cy; //cy.jpg({full: true, output: 'base64'}) 272 | //document.getElementById('detailsTarget')!.innerHTML = base64; 273 | console.log(base64); 274 | $.ajax({ 275 | url: "SaveImage", 276 | data: { "base64": base64 }, 277 | // contentType: "application/json", 278 | method: "POST", 279 | dataType: "json" 280 | }) 281 | }) 282 | .fail(function () { 283 | document.getElementById('detailsTarget')!.innerHTML = "Something went wrong"; 284 | }) 285 | } 286 | 287 | //go("test"); -------------------------------------------------------------------------------- /client/webview/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | body, 7 | .flex-body { 8 | display: flex; 9 | flex: 1;/* no need anymore to deal with height/width */ 10 | flex-direction: column; 11 | } 12 | 13 | .viewport { 14 | display: flex; 15 | flex: 1; 16 | } 17 | /* Wrapping element */ 18 | /* Set some basic padding to keep content from hitting the edges */ 19 | .body-content { 20 | padding-left: 15px; 21 | padding-right: 15px; 22 | } 23 | 24 | /* Set widths on the form inputs since otherwise they're 100% wide */ 25 | input, 26 | select, 27 | textarea { 28 | max-width: 280px; 29 | } 30 | 31 | /* Carousel */ 32 | .carousel-caption p { 33 | font-size: 20px; 34 | line-height: 1.4; 35 | } 36 | 37 | /* Make .svg files in the carousel display properly in older browsers */ 38 | .carousel-inner .item img[src$=".svg"] { 39 | width: 100%; 40 | } 41 | 42 | /* Hide/rearrange for smaller screens */ 43 | @media screen and (max-width: 767px) { 44 | /* Hide captions */ 45 | .carousel-caption { 46 | display: none; 47 | } 48 | } 49 | 50 | .clusters rect { 51 | fill: #00ffd0; 52 | stroke: #999; 53 | stroke-width: 1.5px; 54 | } 55 | 56 | text { 57 | font-weight: 300; 58 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; 59 | font-size: 14px; 60 | color: --vscode-editor-foreground; 61 | } 62 | 63 | .node rect { 64 | stroke: #999; 65 | fill: #fff; 66 | stroke-width: 1.5px; 67 | } 68 | 69 | .edgePath path { 70 | stroke: #333; 71 | stroke-width: 1.5px; 72 | } 73 | 74 | /* This styles the title of the tooltip */ 75 | .tooltip .name { 76 | font-size: 1.5em; 77 | font-weight: bold; 78 | color: #60b1fc; 79 | margin: 0; 80 | } 81 | 82 | /* This styles the body of the tooltip */ 83 | .tooltip .description { 84 | font-size: 1.2em; 85 | } 86 | 87 | svg { 88 | border: 1px solid #ccc; 89 | overflow: hidden; 90 | margin: 0 auto; 91 | } 92 | 93 | .edgePath path.path { 94 | stroke: #333; 95 | fill: none; 96 | stroke-width: 1.5px; 97 | } 98 | 99 | #cy { 100 | border: 1px solid #ddd; 101 | border-radius: 0.25em; 102 | margin:0.25em; 103 | position: relative; 104 | flex:1; 105 | } 106 | .cy-container{ 107 | flex:1; 108 | flex-direction: row; 109 | display: flex 110 | 111 | } 112 | .cwtools-text-center { 113 | text-align: center 114 | } 115 | .cwtools-table { 116 | width: 100% 117 | } -------------------------------------------------------------------------------- /fsharp-language-server.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{4F84437C-65AA-4A9C-A864-87D07746F847}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{29A9E10D-C8C7-416D-B32D-820658A129A6}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Main", "src\Main\Main.fsproj", "{D0C97655-008A-4376-87A3-FD072D076F16}" 14 | EndProject 15 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LSP", "src\LSP\LSP.fsproj", "{AD9284CA-9934-4A4B-9C5C-AE680DFE0451}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{520503FB-4F6E-49F3-8F99-04C04134CD02}" 18 | EndProject 19 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "build", "build\build.fsproj", "{38151F7F-F903-4EEC-A6D8-325FC534FBDC}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Debug|x64 = Debug|x64 25 | Debug|x86 = Debug|x86 26 | Release|Any CPU = Release|Any CPU 27 | Release|x64 = Release|x64 28 | Release|x86 = Release|x86 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|x64.ActiveCfg = Debug|x64 37 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|x64.Build.0 = Debug|x64 38 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|x86.ActiveCfg = Debug|x86 39 | {D0C97655-008A-4376-87A3-FD072D076F16}.Debug|x86.Build.0 = Debug|x86 40 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|x64.ActiveCfg = Release|x64 43 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|x64.Build.0 = Release|x64 44 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|x86.ActiveCfg = Release|x86 45 | {D0C97655-008A-4376-87A3-FD072D076F16}.Release|x86.Build.0 = Release|x86 46 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|x64.ActiveCfg = Debug|x64 49 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|x64.Build.0 = Debug|x64 50 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|x86.ActiveCfg = Debug|x86 51 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Debug|x86.Build.0 = Debug|x86 52 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|x64.ActiveCfg = Release|x64 55 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|x64.Build.0 = Release|x64 56 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|x86.ActiveCfg = Release|x86 57 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451}.Release|x86.Build.0 = Release|x86 58 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|x64.ActiveCfg = Debug|x64 61 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|x64.Build.0 = Debug|x64 62 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|x86.ActiveCfg = Debug|x86 63 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Debug|x86.Build.0 = Debug|x86 64 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|x64.ActiveCfg = Release|x64 67 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|x64.Build.0 = Release|x64 68 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|x86.ActiveCfg = Release|x86 69 | {D9DA28FD-0027-4B97-8112-C89A20B13DF0}.Release|x86.Build.0 = Release|x86 70 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|x64.ActiveCfg = Debug|x64 73 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|x64.Build.0 = Debug|x64 74 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|x86.ActiveCfg = Debug|x86 75 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Debug|x86.Build.0 = Debug|x86 76 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|x64.ActiveCfg = Release|x64 79 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|x64.Build.0 = Release|x64 80 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|x86.ActiveCfg = Release|x86 81 | {72DA2A69-2854-4DD5-A6EA-060F6709A605}.Release|x86.Build.0 = Release|x86 82 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|x64.ActiveCfg = Debug|Any CPU 85 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|x64.Build.0 = Debug|Any CPU 86 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|x86.ActiveCfg = Debug|Any CPU 87 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Debug|x86.Build.0 = Debug|Any CPU 88 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|Any CPU.Build.0 = Release|Any CPU 90 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|x64.ActiveCfg = Release|Any CPU 91 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|x64.Build.0 = Release|Any CPU 92 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|x86.ActiveCfg = Release|Any CPU 93 | {38DCEDF7-3553-4A5D-A9DB-49FA457C4311}.Release|x86.Build.0 = Release|Any CPU 94 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 95 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 96 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|x64.ActiveCfg = Debug|Any CPU 97 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|x64.Build.0 = Debug|Any CPU 98 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|x86.ActiveCfg = Debug|Any CPU 99 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Debug|x86.Build.0 = Debug|Any CPU 100 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 101 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|Any CPU.Build.0 = Release|Any CPU 102 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|x64.ActiveCfg = Release|Any CPU 103 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|x64.Build.0 = Release|Any CPU 104 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|x86.ActiveCfg = Release|Any CPU 105 | {38151F7F-F903-4EEC-A6D8-325FC534FBDC}.Release|x86.Build.0 = Release|Any CPU 106 | EndGlobalSection 107 | GlobalSection(NestedProjects) = preSolution 108 | {D0C97655-008A-4376-87A3-FD072D076F16} = {29A9E10D-C8C7-416D-B32D-820658A129A6} 109 | {AD9284CA-9934-4A4B-9C5C-AE680DFE0451} = {29A9E10D-C8C7-416D-B32D-820658A129A6} 110 | EndGlobalSection 111 | EndGlobal 112 | -------------------------------------------------------------------------------- /fsharp-language-server.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | <AssemblyExplorer> 3 | <Assembly Path="C:\Users\Thomas\Git\cwtools-vscode\paket-files\git\localfilesystem\cwtools\CWTools\bin\Debug\netstandard2.0\CWTools.dll" /> 4 | </AssemblyExplorer> -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "allowPrerelease": false, 4 | "version": "5.0.400", 5 | "rollForward": "latestMajor" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">=16.0.0" 4 | }, 5 | "dependencies": { 6 | "executable": "4.1.1", 7 | "vscode-languageclient": "^6.1.3" 8 | }, 9 | "devDependencies": { 10 | "@rollup/plugin-commonjs": "^25.0.5", 11 | "@rollup/plugin-node-resolve": "^15.2.2", 12 | "@types/cytoscape": "3.14.7", 13 | "@types/glob": "^7.1.3", 14 | "@types/handlebars": "^4.0.40", 15 | "@types/mocha": "^5.2.7", 16 | "@types/node": "^16.18.34", 17 | "@types/qtip2": "^2.2.30", 18 | "@types/vscode": "^1.83.0", 19 | "@vscode/vsce": "^2.22.0", 20 | "copyfiles": "^2.3.0", 21 | "cytoscape": "^3.15.2", 22 | "cytoscape-canvas": "3.0.1", 23 | "cytoscape-elk": "github:jfstephe/cytoscape.js-elk", 24 | "cytoscape-popper": "1.0.7", 25 | "glob": "^7.1.6", 26 | "merge-images": "1.2.0", 27 | "mocha": "^6.2.3", 28 | "npm": "^6.14.8", 29 | "popper.js": "^1.15.0", 30 | "rollup-plugin-hypothetical": "^2.1.0", 31 | "rollup-plugin-typescript2": "^0.36.0", 32 | "tippy.js": "^4.3.5", 33 | "tslib": "^1.13.0", 34 | "typescript": "^5.2.2", 35 | "vscode-test": "^1.4.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | cache ./nupkgs versions: current 3 | 4 | nuget System.Text.Encoding.CodePages # 5 | nuget FParsec 1.0.4-RC3 # 6 | nuget FSharp.Data 3.0.1 # 7 | nuget FSharp.Collections.ParallelSeq # 8 | nuget DotNet.Glob # 9 | nuget FSharpx.Collections # 10 | nuget Sandwych.QuickGraph.Core # 11 | nuget FsPickler # 12 | nuget FSharp.Core # 13 | nuget VDS.Common 14 | 15 | group WindowsLibGit 16 | source https://www.nuget.org/api/v2 17 | cache ./nupkgs versions: current 18 | nuget LibGit2Sharp 0.26.2 19 | nuget LibGit2Sharp.NativeBinaries 20 | 21 | group LinuxLibGit 22 | source https://www.nuget.org/api/v2 23 | cache ./nupkgs versions: current 24 | nuget LibGit2Sharp 0.27.0-preview-0096 25 | nuget LibGit2Sharp.NativeBinaries 26 | 27 | group git 28 | #git file:///C:/users/thomas/git/cwtools master 29 | git https://www.github.com/cwtools/cwtools master 30 | 31 | group build 32 | source https://api.nuget.org/v3/index.json 33 | storage: none 34 | framework: net8.0 35 | 36 | #nuget FSharp.Core 4.7.0.0 37 | #nuget Microsoft.Build 17.3.2 38 | #nuget Microsoft.Build.Framework 17.3.2 39 | #nuget Microsoft.Build.Tasks.Core 17.3.2 40 | #nuget Microsoft.Build.Utilities.Core 17.3.2 41 | nuget Fake.Core 42 | nuget Fake.Core.Target 43 | nuget Fake.IO.FileSystem 44 | nuget Fake.DotNet.Cli 45 | nuget Fake.DotNet.Paket 46 | nuget Fake.JavaScript.Npm 47 | nuget Fake.Core.UserInput 48 | nuget Fake.Tools.Git 49 | nuget Fake.Core.ReleaseNotes 50 | nuget Fake.Api.GitHub 51 | nuget FSharp.Collections.ParallelSeq 52 | nuget MSBuild.StructuredLogger >= 2.1.784 -------------------------------------------------------------------------------- /release/.vscodeignore: -------------------------------------------------------------------------------- 1 | .cwtools/ -------------------------------------------------------------------------------- /release/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Boby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | The contents of the src/LSP directory are derived, Copyright (c) [F# Language Server Contributors](https://github.com/fsprojects/fsharp-language-server/graphs/contributors) -------------------------------------------------------------------------------- /release/README.md: -------------------------------------------------------------------------------- 1 | # [CWTools: Paradox Language Services](https://marketplace.visualstudio.com/items/tboby.cwtools-vscode) 2 | 3 | **Paradox Language Features for Visual Studio Code** 4 | 5 | ## Disclaimer 6 | 7 | This extension is still in preview, it may not work, it may stop working at any time. 8 | **Make backups of your mod files.** 9 | 10 | ## Supported games 11 | 12 | * Stellaris 13 | * Hearts of Iron IV 14 | * Europa Universalis IV 15 | * Imperator: Rome - outdated, help needed 16 | * Crusader Kings II - partial 17 | * Crusader Kings III - in progress, help needed 18 | * Victoria 3 - in progress, help needed 19 | 20 | ## Features 21 | 22 | * Immediate highlighting of syntax errors 23 | * Autocomplete while you type, providing descriptions when available 24 | * Tooltips on hover showing: 25 | * Related localisation 26 | * Documentation for that element 27 | * Scope context at that position 28 | * A wide range of validators for common, interface, and events, checking 29 | * That required localisation keys are defined 30 | * Existence of effects/triggers/modifiers 31 | * Scope context for used effects/triggers/modifiers 32 | * Usage of scripted effects/triggers 33 | * Correct entries for weights/AI_chance/etc 34 | * That event\_targets are saved before they're used 35 | * That referenced sprites and graphics files exist 36 | * and a number of other specific validators 37 | * "Code actions" to generate .yml for missing localisation 38 | 39 | ### Completion 40 | 41 | ![Completion](./docs/completion.gif) 42 | 43 | ### Tooltips 44 | 45 | ![Tooltips](./docs/tooltips.gif) 46 | 47 | ### Scope tooltips 48 | 49 | ![Scope tooltips](./docs/scopetooltip.gif) 50 | 51 | ### Scope errors 52 | 53 | ![Scope ](./docs/scopeerror.gif) 54 | 55 | ### Localisation error 56 | 57 | ![Localisation error](./docs/localisationerror.gif) 58 | 59 | ### Go to definition 60 | 61 | ![Go to definition](./docs/gotodef.gif) 62 | 63 | ### Find all references 64 | 65 | ![Find all references](./docs/findallrefs.png) 66 | 67 | ## Usage 68 | 69 | 1. Install this extension 70 | 2. If on linux, possibly follow [these instructions](https://code.visualstudio.com/docs/setup/linux#_error-enospc) 71 | 3. If on linux, install libcurl3 72 | 4. Either open your mod folder directly 73 | 5. or open the Game folder containing your mods. E.g. for Stellaris this can be one of: 74 | * "C:\Users\name\Paradox Interactive\Stellaris" 75 | * "C:\Program Files(x86)\Steam\steamapps\common\Stellaris" 76 | 77 | or on linux 78 | * "/home/name/.local/share/Paradox Interactive/Stellars" 79 | * "/home/name/.steam/steam/steamapps/common/Stellaris" 80 | 6. Follow the prompts to select your vanilla folder 81 | 7. Edit files and watch syntax errors show up when you make mistakes 82 | 8. Wait up to a minute for the extension to scan all your mods and find all errors 83 | 84 | ## Links 85 | 86 | * [vic2-config](https://github.com/cwtools/cwtools-vic2-config) 87 | * [vic3-config](https://github.com/cwtools/cwtools-vic3-config) 88 | * [ck2-config](https://github.com/cwtools/cwtools-ck2-config) 89 | * [eu4-config](https://github.com/cwtools/cwtools-eu4-config) 90 | * [hoi4-config](https://github.com/cwtools/cwtools-hoi4-config) 91 | * [stellaris-config](https://github.com/cwtools/cwtools-stellaris-config) 92 | * [ir-config](https://github.com/cwtools/cwtools-ir-config) 93 | * [ck3-config](https://github.com/cwtools/cwtools-ck3-config) 94 | -------------------------------------------------------------------------------- /release/docs/completion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/completion.gif -------------------------------------------------------------------------------- /release/docs/cwtools logo purp.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/cwtools logo purp.xcf -------------------------------------------------------------------------------- /release/docs/cwtools logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/cwtools logo.xcf -------------------------------------------------------------------------------- /release/docs/cwtools_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/cwtools_logo.png -------------------------------------------------------------------------------- /release/docs/findallrefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/findallrefs.png -------------------------------------------------------------------------------- /release/docs/gotodef.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/gotodef.gif -------------------------------------------------------------------------------- /release/docs/localisationerror.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/localisationerror.gif -------------------------------------------------------------------------------- /release/docs/scopeerror.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/scopeerror.gif -------------------------------------------------------------------------------- /release/docs/scopetooltip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/scopetooltip.gif -------------------------------------------------------------------------------- /release/docs/tooltips.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/release/docs/tooltips.gif -------------------------------------------------------------------------------- /release/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#" 4 | }, 5 | "brackets": [ 6 | ["{", "}"] 7 | ], 8 | "autoClosingPairs": [ 9 | ["{", "}"], 10 | ["\"", "\""], 11 | ["'", "'"] 12 | ], 13 | "surroundingPairs": [ 14 | ["{", "}"], 15 | ["\"", "\""], 16 | ["'", "'"] 17 | ], 18 | "indentationRules": { 19 | "increaseIndentPattern": "((\\{\\s*))$", 20 | "decreaseIndentPattern": "^\\s*((\\}))" 21 | }, 22 | "wordPattern": "\"?([^\\s]+)\"?" 23 | } -------------------------------------------------------------------------------- /release/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cwtools-vscode", 3 | "version": "0.10.25", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cwtools-vscode", 9 | "version": "0.10.25", 10 | "license": "MIT", 11 | "dependencies": { 12 | "executable": "4.1.1", 13 | "vscode-languageclient": "^6.1.3" 14 | }, 15 | "engines": { 16 | "vscode": "^1.83.0" 17 | } 18 | }, 19 | "node_modules/executable": { 20 | "version": "4.1.1", 21 | "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", 22 | "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", 23 | "dependencies": { 24 | "pify": "^2.2.0" 25 | }, 26 | "engines": { 27 | "node": ">=4" 28 | } 29 | }, 30 | "node_modules/pify": { 31 | "version": "2.3.0", 32 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 33 | "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 34 | "engines": { 35 | "node": ">=0.10.0" 36 | } 37 | }, 38 | "node_modules/semver": { 39 | "version": "6.3.1", 40 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 41 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 42 | "bin": { 43 | "semver": "bin/semver.js" 44 | } 45 | }, 46 | "node_modules/vscode-jsonrpc": { 47 | "version": "5.0.1", 48 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz", 49 | "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==", 50 | "engines": { 51 | "node": ">=8.0.0 || >=10.0.0" 52 | } 53 | }, 54 | "node_modules/vscode-languageclient": { 55 | "version": "6.1.4", 56 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.4.tgz", 57 | "integrity": "sha512-EUOU+bJu6axmt0RFNo3nrglQLPXMfanbYViJee3Fbn2VuQoX0ZOI4uTYhSRvYLP2vfwTP/juV62P/mksCdTZMA==", 58 | "dependencies": { 59 | "semver": "^6.3.0", 60 | "vscode-languageserver-protocol": "3.15.3" 61 | }, 62 | "engines": { 63 | "vscode": "^1.41.0" 64 | } 65 | }, 66 | "node_modules/vscode-languageserver-protocol": { 67 | "version": "3.15.3", 68 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz", 69 | "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==", 70 | "dependencies": { 71 | "vscode-jsonrpc": "^5.0.1", 72 | "vscode-languageserver-types": "3.15.1" 73 | } 74 | }, 75 | "node_modules/vscode-languageserver-types": { 76 | "version": "3.15.1", 77 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", 78 | "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /release/snippets/paradox.json: -------------------------------------------------------------------------------- 1 | { 2 | "Event_Shown": { 3 | "prefix": "event_show", 4 | "body":[ 5 | "${1|country_event,ship_event,planet_event,fleet_event,pop_event,pop_faction_event,event|} = {", 6 | "\tid = ${2:id}", 7 | "\ttitle = ${2:id}.name", 8 | "\tdesc = ${2:id}.desc", 9 | "\tpicture = GFX_evt_mining_station #a name of a picture to display", 10 | "\tlocation = from #A scope to the object that is relevant to the event that player can move to. For example, the planet where event is happening", 11 | "\tshow_sound = #Name of the sound clip to be played when event is shown", 12 | "\tdiplomatic = yes #Makes event look like diplomatic communications. For example, first contact event or a conversation with the subterranean people", 13 | "\tis_triggered_only = yes #The game will not consider this event for starting on its own. Event will need to be triggered by another event, or by some other effect. Most events will use this.", 14 | "\ttrigger = {", 15 | "\t\t", 16 | "\t}", 17 | "\timmediate = {", 18 | "\t\t", 19 | "\t}", 20 | "\toption = {", 21 | "\t\t", 22 | "\t}", 23 | "}" 24 | ], 25 | "description": "Event show" 26 | }, 27 | "Event_Hidden": { 28 | "prefix": "event_hidden", 29 | "body":[ 30 | "${1|country_event,ship_event,planet_event,fleet_event,pop_event,pop_faction_event,event|} = {", 31 | "\tid = ${2:id}", 32 | "\thide_window = yes", 33 | "\ttrigger = {", 34 | "\t\t", 35 | "\t}", 36 | "\tis_triggered_only = yes", 37 | "\timmediate = {", 38 | "\t\t", 39 | "\t}", 40 | "}" 41 | ] 42 | } 43 | } -------------------------------------------------------------------------------- /release/syntaxes/paradox.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Paradox", 4 | "scopeName": "source.mod", 5 | "uuid": "2edebb0f-3089-4abb-8b82-9974e337ad9f", 6 | "foldingStartMarker": "^\\s*#", 7 | "foldingStopMarker": "(?!^[^#])", 8 | "patterns": [ 9 | { 10 | "include": "#namespace" 11 | }, 12 | { 13 | "include": "#code" 14 | } 15 | ], 16 | "repository": { 17 | "namespace": { 18 | "patterns": [ 19 | { 20 | "name": "meta.namespace.paradox", 21 | "match": "^\\s*[^@]?((namespace)\\s[=]\\s([\\w.]+))", 22 | "captures": { 23 | "1": { 24 | "name": "meta.namespace.identifier.txt" 25 | }, 26 | "2": { 27 | "name": "keyword.other.namespace.txt" 28 | }, 29 | "3": { 30 | "name": "entity.name.type.namespace.txt" 31 | } 32 | } 33 | } 34 | ] 35 | }, 36 | "id": { 37 | "patterns": [ 38 | { 39 | "name": "meta.id.paradox", 40 | "match": "^\\s*[^@]?((id)\\s[=]\\s([A-Za-z_]+(.)([0-9]+)))", 41 | "captures": { 42 | "1": { 43 | "name": "meta.id.identifier.txt" 44 | }, 45 | "2": { 46 | "name": "keyword.other.id.txt" 47 | }, 48 | "3": { 49 | "name": "entity.name.type.id.txt" 50 | }, 51 | "4": { 52 | "name": "entity.name.type.period.txt" 53 | }, 54 | "5": { 55 | "name": "entity.name.type.number.txt" 56 | } 57 | } 58 | } 59 | ] 60 | }, 61 | "keywords": { 62 | "patterns": [ 63 | { 64 | "name": "keyword.control.paradox", 65 | "match": "\\b(NOT_USED_1)\\b" 66 | }, 67 | { 68 | "name": "variable.language.description.scope.paradox", 69 | "match": "\\b(NOT_USED_2)\\b" 70 | }, 71 | { 72 | "name": "variable.language.multi_scopes.paradox", 73 | "match": "\\b(ROOT|root|FROM|from|FROMFROM|fromfrom|FROMFROMFROM|fromfromfrom|FROMFROMFROMFROM|fromfromfromfrom|PREV|prev|PREVPREV|prevprev|PREVPREVPREV|prevprevprev|PREVPREVPREVPREV|prevprevprevprev|THIS|this)\\b" 74 | }, 75 | { 76 | "name": "variable.language.other.paradox", 77 | "match": "\\b(variable|hidden_effect|default|random|debug_scope_type|save_event_target_as|custom_tooltip|custom_tooltip_fail|parameter|hidden|hidden_trigger)\\b" 78 | }, 79 | { 80 | "name": "variable.language.conditions.paradox", 81 | "match": "\\b(custom_tooltip|IF|if|hidden_trigger|exists|nor|nand|limit|yes|no|not|and|or|if|else|NOR|always|exists)\\b" 82 | }, 83 | { 84 | "name": "variable.language.definition_tokens.paradox", 85 | "match": "\\b(name|option|id|title|desc|picture|is_triggered_only|immediate|trigger|icon|sound|mean_time_to_happen|fire_only_once|duration|hide_window|modifiers|time|to|ratio|icon_scale|localization|portraits|event_scope|potential|text|fail_text|success_text|allow|active|valid|which|days|event_chain|small_icon|events|ai_weight|ai_allow|weight|modifier|monthly|accumulative|decay|months|unique|key|flag|min|max|add|factor|tech|length|random_weight|value|scope|who|effect|amount|context|show_name|tooltip|description|factor)\\b" 86 | }, 87 | { 88 | "name": "variable.constant.paradox", 89 | "match": "@\\w+" 90 | }, 91 | { 92 | "name": "variable.boolean.paradox", 93 | "match": "\\b(or|and|not|nor|nand|OR|AND|NOT|NOR|NAND)\\b" 94 | }, 95 | { 96 | "name": "variable.event.target.paradox", 97 | "match": "(event_target)(\\:)([\\w]*)", 98 | "captures": { 99 | "1": { 100 | "name": "event_target.identifier.paradox" 101 | }, 102 | "2": { 103 | "name": "punctuation.event_target.colon.paradox" 104 | }, 105 | "3": { 106 | "name": "event_target.name.paradox" 107 | } 108 | } 109 | }, 110 | { 111 | "name": "variable.lhs.bracket.paradox", 112 | "match": "(\\w|\\.|-|:)+(?=\\s?(=|>|<|<=|>=)\\s?{)" 113 | }, 114 | { 115 | "name": "variable.lhs.paradox", 116 | "match": "(\\w|\\.|-|:)+(?=\\s?(=|>|<|<=|>=))" 117 | }, 118 | { 119 | "name": "variable.bracket.keywords", 120 | "match": "\\{\\s+[\\w+\\s+]+\\}" 121 | }, 122 | { 123 | "name": "variable.language.description.paradox", 124 | "match": "\\b(NOT_USED_3)\\b" 125 | } 126 | ] 127 | }, 128 | "constants": { 129 | "patterns": [ 130 | { 131 | "name": "constant.language.paradox", 132 | "match": "(?<=(=|>|<))(\\s*)((yes|no)+(\\s|=))" 133 | }, 134 | { 135 | "name": "constant.numeric.paradox", 136 | "match": "(?<=(\\s|=|>|<))-?\\b(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))\\b" 137 | }, 138 | { 139 | "name": "constant.rhs.number.paradox", 140 | "match": "(?<=(=|>|<))(\\s*)((\\d|\\.)+(\\s|=))" 141 | }, 142 | { 143 | "name": "constant.rhs.paradox", 144 | "match": "(?<=(=|>|<))(\\s*)((\\w|\\.)+(\\s|=))" 145 | }, 146 | { 147 | "name": "constant.rhs.string.paradox", 148 | "match": "(\\\")[\\w\\s:.\\[\\]]*(\\\")" 149 | }, 150 | { 151 | "include": "#strings" 152 | } 153 | ] 154 | }, 155 | "comment": { 156 | "patterns": [ 157 | { 158 | "name": "comment.line.number-sign.paradox", 159 | "begin": "#", 160 | "captures": { 161 | "1": { 162 | "name": "punctuation.definition.comment.paradox" 163 | } 164 | }, 165 | "end": "$\\n?" 166 | } 167 | ] 168 | }, 169 | "block": { 170 | "patterns": [ 171 | { 172 | "begin": "(?<==)\\s*{", 173 | "beginCaptures": { 174 | "0": { 175 | "name": "punctuation.section.block.begin.paradox" 176 | } 177 | }, 178 | "end": "}", 179 | "endCaptures": { 180 | "0": { 181 | "name": "punctuation.section.block.end.paradox" 182 | } 183 | }, 184 | "name": "meta.block.paradox", 185 | "patterns": [ 186 | { 187 | "include": "#code" 188 | } 189 | ] 190 | } 191 | ] 192 | }, 193 | "code": { 194 | "patterns": [ 195 | { 196 | "include": "#block" 197 | }, 198 | { 199 | "include": "#comment" 200 | }, 201 | { 202 | "include": "#id" 203 | }, 204 | { 205 | "include": "#keywords" 206 | }, 207 | { 208 | "include": "#constants" 209 | }, 210 | { 211 | "include": "#variables" 212 | } 213 | ] 214 | }, 215 | "variables": { 216 | "patterns": [ 217 | { 218 | "begin": "[^\\S\\n]*(?() 40 | /// Replace a section of an open file 41 | let patch(doc: VersionedTextDocumentIdentifier, range: Range, text: string): unit = 42 | let file = FileInfo(doc.uri.LocalPath) 43 | let existing = activeDocuments.[file.FullName] 44 | let startOffset, endOffset = findRange(existing.text, range) 45 | existing.text.Remove(startOffset, endOffset - startOffset) |> ignore 46 | existing.text.Insert(startOffset, text) |> ignore 47 | existing.version <- doc.version 48 | /// Replace the entire contents of an open file 49 | let replace(doc: VersionedTextDocumentIdentifier, text: string): unit = 50 | let file = FileInfo(doc.uri.LocalPath) 51 | let existing = activeDocuments.[file.FullName] 52 | existing.text.Clear() |> ignore 53 | existing.text.Append(text) |> ignore 54 | existing.version <- doc.version 55 | 56 | member this.Open(doc: DidOpenTextDocumentParams): unit = 57 | let file = FileInfo(doc.textDocument.uri.LocalPath) 58 | let text = StringBuilder(doc.textDocument.text) 59 | let version = {text = text; version = doc.textDocument.version} 60 | activeDocuments.[file.FullName] <- version 61 | 62 | member this.Change(doc: DidChangeTextDocumentParams): unit = 63 | let file = FileInfo(doc.textDocument.uri.LocalPath) 64 | let existing = activeDocuments.[file.FullName] 65 | if doc.textDocument.version <= existing.version then 66 | let oldVersion = existing.version 67 | let newVersion = doc.textDocument.version 68 | dprintfn "Change %d to doc %s is earlier than existing version %d" newVersion file.Name oldVersion 69 | else 70 | for change in doc.contentChanges do 71 | match change.range with 72 | | Some range -> patch(doc.textDocument, range, change.text) 73 | | None -> replace(doc.textDocument, change.text) 74 | 75 | member this.GetText(file: FileInfo): string option = 76 | let found, value = activeDocuments.TryGetValue(file.FullName) 77 | if found then Some(value.text.ToString()) else None 78 | 79 | member this.GetVersion(file: FileInfo): int option = 80 | let found, value = activeDocuments.TryGetValue(file.FullName) 81 | if found then Some(value.version) else None 82 | 83 | member this.Get(file: FileInfo): option = 84 | let found, value = activeDocuments.TryGetValue(file.FullName) 85 | if found then Some(value.text.ToString(), value.version) else None 86 | 87 | member this.Close(doc: DidCloseTextDocumentParams): unit = 88 | let file = FileInfo(doc.textDocument.uri.LocalPath) 89 | activeDocuments.Remove(file.FullName) |> ignore 90 | 91 | member this.OpenFiles(): FileInfo list = 92 | [for file in activeDocuments.Keys do yield FileInfo(file)] -------------------------------------------------------------------------------- /src/LSP/LSP.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/LSP/Log.fs: -------------------------------------------------------------------------------- 1 | module LSP.Log 2 | 3 | let diagnosticsLog = ref stderr 4 | 5 | /// Print to LSP.Log.diagnosticsLog, which is stderr by default but can be redirected 6 | let dprintfn(fmt: Printf.TextWriterFormat<'T>): 'T = 7 | Printf.fprintfn !diagnosticsLog fmt -------------------------------------------------------------------------------- /src/LSP/Parser.fs: -------------------------------------------------------------------------------- 1 | module LSP.Parser 2 | 3 | open LSP.Log 4 | open System 5 | open FSharp.Data 6 | open FSharp.Data.JsonExtensions 7 | open LSP.Json.Ser 8 | open LSP.Types 9 | open Types 10 | 11 | type RawMessage = { 12 | id: int option 13 | method: string option 14 | ``params``: JsonValue option 15 | result: JsonValue option 16 | error: JsonValue option 17 | } 18 | 19 | let parseTextDocumentSaveReason(i: int): TextDocumentSaveReason = 20 | match i with 21 | | 1 -> TextDocumentSaveReason.Manual 22 | | 2 -> TextDocumentSaveReason.AfterDelay 23 | | 3 -> TextDocumentSaveReason.FocusOut 24 | | _ -> raise(Exception(sprintf "%d is not a known TextDocumentSaveReason" i)) 25 | 26 | let parseFileChangeType(i: int): FileChangeType = 27 | match i with 28 | | 1 -> FileChangeType.Created 29 | | 2 -> FileChangeType.Changed 30 | | 3 -> FileChangeType.Deleted 31 | | _ -> raise(Exception(sprintf "%d is not a known FileChangeType" i)) 32 | 33 | let parseTrace(text: string): Trace = 34 | match text with 35 | | "off" -> Trace.Off 36 | | "messages" -> Trace.Messages 37 | | "verbose" -> Trace.Verbose 38 | | _ -> raise(Exception(sprintf "Unexpected trace %s" text)) 39 | 40 | let parseCompletionItemKind(i: int): CompletionItemKind = 41 | match i with 42 | | 1 -> CompletionItemKind.Text 43 | | 2 -> CompletionItemKind.Method 44 | | 3 -> CompletionItemKind.Function 45 | | 4 -> CompletionItemKind.Constructor 46 | | 5 -> CompletionItemKind.Field 47 | | 6 -> CompletionItemKind.Variable 48 | | 7 -> CompletionItemKind.Class 49 | | 8 -> CompletionItemKind.Interface 50 | | 9 -> CompletionItemKind.Module 51 | | 10 -> CompletionItemKind.Property 52 | | 11 -> CompletionItemKind.Unit 53 | | 12 -> CompletionItemKind.Value 54 | | 13 -> CompletionItemKind.Enum 55 | | 14 -> CompletionItemKind.Keyword 56 | | 15 -> CompletionItemKind.Snippet 57 | | 16 -> CompletionItemKind.Color 58 | | 17 -> CompletionItemKind.File 59 | | 18 -> CompletionItemKind.Reference 60 | | 19 -> CompletionItemKind.Folder 61 | | 20 -> CompletionItemKind.EnumMember 62 | | 21 -> CompletionItemKind.Constant 63 | | 22 -> CompletionItemKind.Struct 64 | | 23 -> CompletionItemKind.Event 65 | | 24 -> CompletionItemKind.Operator 66 | | 25 -> CompletionItemKind.TypeParameter 67 | | _ -> raise(Exception(sprintf "%d is not a known CompletionItemKind" i)) 68 | 69 | let parseInsertTextFormat(i: int): InsertTextFormat = 70 | match i with 71 | | 1 -> InsertTextFormat.PlainText 72 | | 2 -> InsertTextFormat.Snippet 73 | | _ -> raise(Exception(sprintf "%d is not a known InsertTextFormat" i)) 74 | 75 | let parseDiagnosticSeverity(i: int): DiagnosticSeverity = 76 | match i with 77 | | 1 -> DiagnosticSeverity.Error 78 | | 2 -> DiagnosticSeverity.Warning 79 | | 3 -> DiagnosticSeverity.Information 80 | | 4 -> DiagnosticSeverity.Hint 81 | | _ -> raise(Exception(sprintf "%d is not a known DiagnosticSeverity" i)) 82 | 83 | let parseMarkupKind(s: string): MarkupKind = 84 | match s with 85 | | "plaintext" -> MarkupKind.PlainText 86 | | "markdown" -> MarkupKind.Markdown 87 | | _ -> raise(Exception(sprintf "%s is not a known MarkupKind" s)) 88 | 89 | let parseCompletionTriggerKind(i : int): CompletionTriggerKind = 90 | match i with 91 | | 1 -> CompletionTriggerKind.Invoked 92 | | 2 -> CompletionTriggerKind.TriggerCharacter 93 | | 3 -> CompletionTriggerKind.TriggerForIncompleteCompletions 94 | | _ -> raise(Exception(sprintf "%d is not a known CompletionTriggerKind" i)) 95 | 96 | let private readOptions = 97 | { defaultJsonReadOptions 98 | with customReaders = [ parseTextDocumentSaveReason 99 | parseFileChangeType 100 | parseTrace 101 | parseCompletionItemKind 102 | parseInsertTextFormat 103 | parseDiagnosticSeverity 104 | parseMarkupKind 105 | parseCompletionTriggerKind ] } 106 | 107 | let private deserializeRawMessage = JsonValue.Parse >> deserializerFactory readOptions 108 | 109 | type Message = 110 | | RequestMessage of id: int * method: string * json: JsonValue 111 | | NotificationMessage of method: string * json: JsonValue option 112 | | ResponseMessage of id: int * response: JsonValue 113 | 114 | let parseMessage(jsonText: string): Message = 115 | let raw = deserializeRawMessage jsonText 116 | match raw.id, raw.method, raw.``params``, raw.result, raw.error with 117 | | Some id, Some method, Some p, _, _ -> RequestMessage (id, method, p) 118 | | Some id, _, _, Some r, _ -> ResponseMessage (id, r) 119 | | Some id, None, _, _, Some e -> ResponseMessage(id, JsonValue.Null) 120 | | Some id, None, _, _, _ -> raise(Exception(sprintf "Request message with id %d missing params. Text: %s" id jsonText)) 121 | | None, Some m, _, _ , _-> NotificationMessage (m, raw.``params``) 122 | | Some id, Some method, _, _, _ -> RequestMessage (id, method, JsonValue.Null) 123 | | _ -> raise(Exception(sprintf "Message %s doesn't match format expected" jsonText)) 124 | 125 | let parseDidChangeConfigurationParams = deserializerFactory readOptions 126 | 127 | let parseDidOpenTextDocumentParams = deserializerFactory readOptions 128 | 129 | let parseDidChangeTextDocumentParams = deserializerFactory readOptions 130 | 131 | let parseWillSaveTextDocumentParams = deserializerFactory readOptions 132 | 133 | let parseDidSaveTextDocumentParams = deserializerFactory readOptions 134 | 135 | let parseDidCloseTextDocumentParams = deserializerFactory readOptions 136 | 137 | let parseDidFocusFileParams = deserializerFactory readOptions 138 | 139 | let parseDidChangeWatchedFilesParams = deserializerFactory readOptions 140 | 141 | let parseNotification(method: string, maybeBody: JsonValue option): Notification = 142 | match method, maybeBody with 143 | | "initialized", _ -> Initialized 144 | | "exit", _ -> raise(Exception"exit message should terminated stream before reaching this point") 145 | | "workspace/didChangeConfiguration", Some json -> DidChangeConfiguration (parseDidChangeConfigurationParams json) 146 | | "textDocument/didOpen", Some json -> DidOpenTextDocument (parseDidOpenTextDocumentParams json) 147 | | "textDocument/didChange", Some json -> DidChangeTextDocument (parseDidChangeTextDocumentParams json) 148 | | "textDocument/willSave", Some json -> WillSaveTextDocument (parseWillSaveTextDocumentParams json) 149 | | "textDocument/didSave", Some json -> DidSaveTextDocument (parseDidSaveTextDocumentParams json) 150 | | "textDocument/didClose", Some json -> DidCloseTextDocument (parseDidCloseTextDocumentParams json) 151 | | "workspace/didChangeWatchedFiles", Some json -> DidChangeWatchedFiles (parseDidChangeWatchedFilesParams json) 152 | | "didFocusFile", Some json -> DidFocusFile (parseDidFocusFileParams json) 153 | | _, None -> 154 | dprintfn "%s is not a known notification, or it is expected to contain a body" method 155 | OtherNotification method 156 | | _, _ -> 157 | dprintfn "%s is not a known notification" method 158 | OtherNotification method 159 | 160 | type InitializeParamsRaw = { 161 | processId: int option 162 | rootUri: Uri option 163 | initializationOptions: JsonValue option 164 | capabilities: JsonValue 165 | trace: Trace option 166 | workspaceFolders : WorkspaceFolder list 167 | } 168 | 169 | let private parseCapabilities(nested: JsonValue): Map = 170 | let rec flatten(path: string, node: JsonValue) = 171 | seq { 172 | for (key, value) in node.Properties do 173 | let newPath = path + "." + key 174 | match value with 175 | | JsonValue.Boolean setting -> yield (newPath, setting) 176 | | _ -> yield! flatten(newPath, value) 177 | } 178 | let kvs = seq { 179 | for (key, value) in nested.Properties do 180 | if key <> "experimental" then 181 | yield! flatten(key, value) 182 | } 183 | Map.ofSeq kvs 184 | 185 | let private parseInitializeParams(raw: InitializeParamsRaw): InitializeParams = 186 | { 187 | processId = raw.processId 188 | rootUri = raw.rootUri 189 | initializationOptions = raw.initializationOptions 190 | capabilitiesMap = raw.capabilities |> parseCapabilities 191 | trace = raw.trace 192 | workspaceFolders = raw.workspaceFolders 193 | } 194 | 195 | let parseInitialize = deserializerFactory readOptions >> parseInitializeParams 196 | 197 | let parseTextDocumentPositionParams = deserializerFactory readOptions 198 | 199 | let parseCompletionParams = deserializerFactory readOptions 200 | 201 | let parseCompletionItem = deserializerFactory readOptions 202 | 203 | let parseReferenceParams = deserializerFactory readOptions 204 | 205 | let parseDocumentSymbolParams = deserializerFactory readOptions 206 | 207 | let parseWorkspaceSymbolParams = deserializerFactory readOptions 208 | 209 | let parseDiagnostic = deserializerFactory readOptions 210 | 211 | let parseCodeActionParams = deserializerFactory readOptions 212 | 213 | let parseCodeLensParams = deserializerFactory readOptions 214 | 215 | let parseCodeLens = deserializerFactory readOptions 216 | 217 | let parseDocumentLinkParams = deserializerFactory readOptions 218 | 219 | let parseDocumentLink = deserializerFactory readOptions 220 | 221 | type DocumentFormattingParamsRaw = { 222 | textDocument: TextDocumentIdentifier 223 | options: JsonValue 224 | } 225 | 226 | type DocumentRangeFormattingParamsRaw = { 227 | textDocument: TextDocumentIdentifier 228 | options: JsonValue 229 | range: Range 230 | } 231 | 232 | type DocumentOnTypeFormattingParamsRaw = { 233 | textDocument: TextDocumentIdentifier 234 | options: JsonValue 235 | position: Position 236 | ch: char 237 | } 238 | 239 | let private parseDocumentFormattingOptions(options: JsonValue) = 240 | { 241 | tabSize = options?tabSize.AsInteger() 242 | insertSpaces = options?insertSpaces.AsBoolean() 243 | } 244 | 245 | let private parseDocumentFormattingOptionsMap(options: JsonValue) = 246 | options.Properties 247 | |> Seq.map (fun (key, value) -> (key, value.AsString())) 248 | |> Map.ofSeq 249 | 250 | let private parseDocumentFormattingParamsRaw(raw: DocumentFormattingParamsRaw): DocumentFormattingParams = 251 | { 252 | textDocument = raw.textDocument 253 | options = parseDocumentFormattingOptions raw.options 254 | optionsMap = parseDocumentFormattingOptionsMap raw.options 255 | } 256 | 257 | let private parseDocumentRangeFormattingParamsRaw(raw: DocumentRangeFormattingParamsRaw): DocumentRangeFormattingParams = 258 | { 259 | textDocument = raw.textDocument 260 | options = parseDocumentFormattingOptions raw.options 261 | optionsMap = parseDocumentFormattingOptionsMap raw.options 262 | range = raw.range 263 | } 264 | 265 | let private parseDocumentOnTypeFormattingParamsRaw(raw: DocumentOnTypeFormattingParamsRaw): DocumentOnTypeFormattingParams = 266 | { 267 | textDocument = raw.textDocument 268 | options = parseDocumentFormattingOptions raw.options 269 | optionsMap = parseDocumentFormattingOptionsMap raw.options 270 | position = raw.position 271 | ch = raw.ch 272 | } 273 | 274 | let parseDocumentFormattingParams = deserializerFactory readOptions >> parseDocumentFormattingParamsRaw 275 | 276 | let parseDocumentRangeFormattingParams = deserializerFactory readOptions >> parseDocumentRangeFormattingParamsRaw 277 | 278 | let parseDocumentOnTypeFormattingParams = deserializerFactory readOptions >> parseDocumentOnTypeFormattingParamsRaw 279 | 280 | let parseRenameParams = deserializerFactory readOptions 281 | 282 | let parseExecuteCommandParams = deserializerFactory readOptions 283 | 284 | let parseDidChangeWorkspaceFoldersParams = deserializerFactory readOptions 285 | 286 | let parseRequest(method: string, json: JsonValue): Request = 287 | match method with 288 | | "initialize" -> Initialize(parseInitialize json) 289 | | "shutdown" -> Shutdown 290 | | "textDocument/willSaveWaitUntil" -> WillSaveWaitUntilTextDocument(parseWillSaveTextDocumentParams json) 291 | | "textDocument/completion" -> Completion(parseCompletionParams json) 292 | | "textDocument/hover" -> Hover(parseTextDocumentPositionParams json) 293 | | "completionItem/resolve" -> ResolveCompletionItem(parseCompletionItem json) 294 | | "textDocument/signatureHelp" -> SignatureHelp(parseTextDocumentPositionParams json) 295 | | "textDocument/definition" -> GotoDefinition(parseTextDocumentPositionParams json) 296 | | "textDocument/references" -> FindReferences(parseReferenceParams json) 297 | | "textDocument/documentHighlight" -> DocumentHighlight(parseTextDocumentPositionParams json) 298 | | "textDocument/documentSymbol" -> DocumentSymbols(parseDocumentSymbolParams json) 299 | | "workspace/symbol" -> WorkspaceSymbols(parseWorkspaceSymbolParams json) 300 | | "textDocument/codeAction" -> CodeActions(parseCodeActionParams json) 301 | | "textDocument/codeLens" -> CodeLens(parseCodeLensParams json) 302 | | "codeLens/resolve" -> ResolveCodeLens(parseCodeLens json) 303 | | "textDocument/documentLink" -> DocumentLink(parseDocumentLinkParams json) 304 | | "documentLink/resolve" -> ResolveDocumentLink(parseDocumentLink json) 305 | | "textDocument/formatting" -> DocumentFormatting(parseDocumentFormattingParams json) 306 | | "textDocument/rangeFormatting" -> DocumentRangeFormatting(parseDocumentRangeFormattingParams json) 307 | | "textDocument/onTypeFormatting" -> DocumentOnTypeFormatting(parseDocumentOnTypeFormattingParams json) 308 | | "textDocument/rename" -> Rename(parseRenameParams json) 309 | | "workspace/executeCommand" -> ExecuteCommand(parseExecuteCommandParams json) 310 | | "workspace/didChangeWorkspaceFolders" -> DidChangeWorkspaceFolders(parseDidChangeWorkspaceFoldersParams json) 311 | | _ -> raise(Exception(sprintf "Unexpected request method %s" method)) -------------------------------------------------------------------------------- /src/LSP/Ser.fs: -------------------------------------------------------------------------------- 1 | module LSP.Json.Ser 2 | 3 | open System 4 | open System.Reflection 5 | open Microsoft.FSharp.Reflection 6 | open Microsoft.FSharp.Reflection.FSharpReflectionExtensions 7 | open System.Text.RegularExpressions 8 | open FSharp.Data 9 | 10 | let private escapeChars = Regex("[\t\n\r\"\\\\]", RegexOptions.Compiled) 11 | let private replaceChars = 12 | MatchEvaluator(fun m -> 13 | match m.Value with 14 | | "\\" -> "\\\\" 15 | | "\t" -> "\\t" 16 | | "\n" -> "\\n" 17 | | "\r" -> "\\r" 18 | | "\"" -> "\\\"" 19 | | v -> v) 20 | let private escapeStr(text:string) = 21 | let escaped = escapeChars.Replace(text, replaceChars) 22 | sprintf "\"%s\"" escaped 23 | 24 | let private isOption(t: Type) = 25 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option> 26 | 27 | let private isSeq(t: Type) = 28 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 29 | let private implementsSeq(t: Type) = 30 | let is = t.GetInterfaces() 31 | Seq.exists isSeq is 32 | 33 | let private isList(t: Type) = 34 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 35 | 36 | let private isMap(t: Type) = 37 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 38 | 39 | type JsonWriteOptions = { 40 | customWriters: obj list 41 | } 42 | 43 | let defaultJsonWriteOptions: JsonWriteOptions = { 44 | customWriters = [] 45 | } 46 | 47 | let private matchWriter(t: Type, w: obj): bool = 48 | let domain, _ = w.GetType() |> FSharpType.GetFunctionElements 49 | domain.IsAssignableFrom(t) 50 | 51 | let private findWriter(t: Type, customWriters: obj list): obj option = 52 | let matchT(w: obj) = matchWriter(t, w) 53 | Seq.tryFind matchT customWriters 54 | 55 | let asFun(w: obj): obj -> obj = 56 | let invoke = w.GetType().GetMethod("Invoke") 57 | fun x -> invoke.Invoke(w, [|x|]) 58 | 59 | type MakeHelpers = 60 | static member MakeList<'T> (items: obj seq): 'T list = 61 | [ for i in items do yield i :?> 'T ] 62 | static member MakeMap<'T> (items: (string * obj) seq): Map = 63 | let castV(k: string, v: obj) = (k, v :?> 'T) 64 | let castItems = Seq.map castV items 65 | Map.ofSeq(castItems) 66 | static member MakeOption<'T> (item: obj option): 'T option = 67 | match item with 68 | | None -> None 69 | | Some(i) -> Some(i :?> 'T) 70 | 71 | let private makeList(t: Type, items: obj seq) = 72 | typeof.GetMethod("MakeList").MakeGenericMethod([|t|]).Invoke(null, [|items|]) 73 | 74 | let private makeMap(t: Type, kvs: (string * obj) seq) = 75 | typeof.GetMethod("MakeMap").MakeGenericMethod([|t|]).Invoke(null, [|kvs|]) 76 | 77 | let private makeOption(t: Type, item: obj option) = 78 | typeof.GetMethod("MakeOption").MakeGenericMethod([|t|]).Invoke(null, [|item|]) 79 | 80 | let rec private serializer (depth : int, options: JsonWriteOptions, t: Type): obj -> string = 81 | let custom = findWriter(t, options.customWriters) 82 | if depth >= 20 then 83 | fun _ -> "" 84 | elif custom.IsSome then 85 | let fObj = custom.Value 86 | let fType = fObj.GetType() 87 | let _, range = FSharpType.GetFunctionElements(fType) 88 | let serialize = serializer(depth, options, range) 89 | let transform = asFun(fObj) 90 | fun o -> serialize(transform(o)) 91 | elif t = typeof then 92 | fun o -> sprintf "%b" (unbox o) 93 | elif t = typeof then 94 | fun o -> sprintf "%d" (unbox o) 95 | elif t = typeof then 96 | fun o -> sprintf "%c" (unbox o) |> escapeStr 97 | elif t = typeof then 98 | fun o -> escapeStr (o :?> string) 99 | elif t = typeof then 100 | fun o -> 101 | let uri = o :?> Uri 102 | escapeStr(uri.ToString()) 103 | elif t = typeof then 104 | fun o -> 105 | let asJson = o :?> JsonValue 106 | asJson.ToString(JsonSaveOptions.DisableFormatting) 107 | elif FSharpType.IsRecord t then 108 | let fields = FSharpType.GetRecordFields(t) 109 | let serializers = [|for f in fields do yield fieldSerializer(depth, options, f)|] 110 | fun outer -> 111 | let fieldStrings = [|for f in serializers do yield f(outer)|] 112 | let innerString = String.concat "," fieldStrings 113 | sprintf "{%s}" innerString 114 | elif implementsSeq t then 115 | let [|innerType|] = t.GetGenericArguments() 116 | let serializeInner = serializer(depth, options, innerType) 117 | fun outer -> 118 | let asEnum = outer :?> System.Collections.IEnumerable 119 | let asSeq = Seq.cast(asEnum) 120 | let inners = Seq.map serializeInner asSeq 121 | let join = String.Join(",", inners) 122 | sprintf "[%s]" join 123 | elif isOption t then 124 | let [|innerType|] = t.GetGenericArguments() 125 | let isSomeProp = t.GetProperty("IsSome") 126 | let isSome outer = isSomeProp.GetValue(None, [|outer|]) :?> bool 127 | let valueProp = t.GetProperty("Value") 128 | let serializeInner = serializer(depth, options, innerType) 129 | fun outer -> 130 | if isSome outer then 131 | let value = valueProp.GetValue outer 132 | serializeInner(value) 133 | else "null" 134 | else 135 | raise (Exception (sprintf "Don't know how to serialize %s to JSON" (t.ToString()))) 136 | and fieldSerializer (depth : int, options: JsonWriteOptions, field: PropertyInfo): obj -> string = 137 | let name = escapeStr(field.Name) 138 | let innerSerializer = serializer(depth + 1,options, field.PropertyType) 139 | fun outer -> 140 | let value = field.GetValue(outer) 141 | let json = innerSerializer(value) 142 | sprintf "%s:%s" name json 143 | 144 | let serializerFactory<'T> (options: JsonWriteOptions): 'T -> string = serializer(1, options, typeof<'T>) 145 | 146 | type JsonReadOptions = { 147 | customReaders: obj list 148 | } 149 | 150 | let defaultJsonReadOptions: JsonReadOptions = { 151 | customReaders = [] 152 | } 153 | 154 | let private matchReader(t: Type, w: obj): bool = 155 | let _, range = w.GetType() |> FSharpType.GetFunctionElements 156 | t.IsAssignableFrom(range) 157 | 158 | let private findReader(t: Type, customReaders: obj list): obj option = 159 | let matchT(reader: obj) = matchReader(t, reader) 160 | Seq.tryFind matchT customReaders 161 | 162 | let rec private deserializer<'T> (options: JsonReadOptions, t: Type): JsonValue -> obj = 163 | let custom = findReader(t, options.customReaders) 164 | if custom.IsSome then 165 | let domain, _ = FSharpType.GetFunctionElements(custom.Value.GetType()) 166 | let deserializeDomain = deserializer(options, domain) 167 | let deserializeInner = asFun(custom.Value) 168 | fun j -> deserializeInner(deserializeDomain(j)) 169 | elif t = typeof then 170 | fun j -> box(j.AsBoolean()) 171 | elif t = typeof then 172 | fun j -> box(j.AsInteger()) 173 | elif t = typeof then 174 | fun j -> 175 | let s = j.AsString() 176 | if s.Length = 1 then box(s.[0]) 177 | else raise(Exception(sprintf "Expected char but found '%s'" s)) 178 | elif t = typeof then 179 | fun j -> box(j.AsString()) 180 | elif t = typeof then 181 | fun j -> 182 | // It seems that the Uri(...) constructor assumes the string has already been unescaped 183 | let escaped = j.AsString() 184 | let unescaped = Uri.UnescapeDataString(escaped) 185 | box(Uri(unescaped)) 186 | elif t = typeof then 187 | fun j -> box(j) 188 | elif isList t then 189 | let [|innerType|] = t.GetGenericArguments() 190 | let deserializeInner = deserializer(options, innerType) 191 | fun j -> 192 | let array = j.AsArray() 193 | let parse = Seq.map deserializeInner array 194 | let list = makeList(innerType, parse) 195 | box(list) 196 | elif isMap t then 197 | let [|stringType; valueType|] = t.GetGenericArguments() 198 | if stringType <> typeof then raise (Exception (sprintf "Keys of %A are not strings" t)) 199 | let deserializeInner = deserializer(options, valueType) 200 | fun j -> 201 | let props = j.Properties() 202 | let parse = Seq.map (fun (k, v) -> k, deserializeInner v) props 203 | makeMap(valueType, parse) 204 | elif isOption t then 205 | let [|innerType|] = t.GetGenericArguments() 206 | let deserializeInner = deserializer(options, innerType) 207 | fun j -> 208 | if j = JsonValue.Null then 209 | box(makeOption(innerType, None)) 210 | else 211 | let parse = deserializeInner j 212 | box(makeOption(innerType, Some parse)) 213 | elif FSharpType.IsRecord t then 214 | let fields = FSharpType.GetRecordFields(t) 215 | let readers = [|for f in fields do yield fieldDeserializer(options, f)|] 216 | fun j -> 217 | let array = [| for field, reader in readers do 218 | yield reader j |] 219 | FSharpValue.MakeRecord(t, array) 220 | else 221 | raise (Exception (sprintf "Don't know how to deserialize %A from JSON" t)) 222 | and fieldDeserializer (options: JsonReadOptions, field: PropertyInfo): string * (JsonValue -> obj) = 223 | let deserializeInner = deserializer(options, field.PropertyType) 224 | let deserializeField(j: JsonValue) = 225 | let value = match j.TryGetProperty(field.Name) with Some v -> v | None -> JsonValue.Null 226 | box(deserializeInner(value)) 227 | field.Name, deserializeField 228 | 229 | let deserializerFactory<'T>(options: JsonReadOptions): JsonValue -> 'T = 230 | let d = deserializer(options, typeof<'T>) 231 | fun s -> d(s) :?> 'T -------------------------------------------------------------------------------- /src/LSP/Tokenizer.fs: -------------------------------------------------------------------------------- 1 | module LSP.Tokenizer 2 | 3 | open System 4 | open System.IO 5 | open System.Text 6 | open Log 7 | 8 | type Header = ContentLength of int | EmptyHeader | OtherHeader 9 | 10 | let parseHeader(header: string): Header = 11 | let contentLength = "Content-Length: " 12 | if header.StartsWith(contentLength) then 13 | let tail = header.Substring(contentLength.Length) 14 | let length = Int32.Parse(tail) 15 | ContentLength(length) 16 | elif header = "" then EmptyHeader 17 | else OtherHeader 18 | 19 | let rec private eatWhitespace(client: BinaryReader): char = 20 | let c = client.ReadChar() 21 | if Char.IsWhiteSpace(c) then 22 | eatWhitespace(client) 23 | else 24 | c 25 | 26 | let readLength(byteLength: int, client: BinaryReader): string = 27 | // Somehow, we are getting extra \r\n sequences, only when we compile to a standalone executable 28 | let head = eatWhitespace(client) 29 | let tail = client.ReadBytes(byteLength - 1) 30 | let string = Encoding.UTF8.GetString(tail) 31 | Convert.ToString(head) + string 32 | 33 | let readLine(client: BinaryReader): string option = 34 | let buffer = StringBuilder() 35 | try 36 | let mutable endOfLine = false 37 | while not endOfLine do 38 | let nextChar = client.ReadChar() 39 | if nextChar = '\n' then do 40 | endOfLine <- true 41 | elif nextChar = '\r' then do 42 | client.ReadChar() |> ignore 43 | endOfLine <- true 44 | else do 45 | buffer.Append(nextChar) |> ignore 46 | Some(buffer.ToString()) 47 | with 48 | | :? EndOfStreamException -> 49 | if buffer.Length > 0 then 50 | Some(buffer.ToString()) 51 | else 52 | None 53 | 54 | let tokenize(client: BinaryReader): seq = 55 | seq { 56 | let mutable contentLength = -1 57 | let mutable endOfInput = false 58 | while not endOfInput do 59 | let maybeHeader = readLine(client) 60 | let next = Option.map parseHeader maybeHeader 61 | match next with 62 | | None -> endOfInput <- true 63 | | Some(ContentLength l) -> contentLength <- l 64 | | Some(EmptyHeader) -> yield readLength(contentLength, client) 65 | | _ -> () 66 | } -------------------------------------------------------------------------------- /src/LSP/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwtools/cwtools-vscode/7273459277f58d52393eb5415945efeb1197e3dc/src/LSP/paket.exe -------------------------------------------------------------------------------- /src/LSP/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | FSharp.Collections.ParallelSeq 3 | FSharp.Data -------------------------------------------------------------------------------- /src/Main/Completion.fs: -------------------------------------------------------------------------------- 1 | module Main.Completion 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.IO 6 | open System.Runtime.InteropServices 7 | open CWTools.Common 8 | open CWTools.Games 9 | open CWTools.Utilities.Position 10 | open CWTools.Utilities.StringResource 11 | open FSharp.Data 12 | open LSP 13 | open LSP.Types 14 | open CWTools.Utilities.Utils 15 | 16 | 17 | let completionCache = Dictionary() 18 | let mutable private completionCacheKey = 0 19 | 20 | let addToCache completionItem = 21 | let key = completionCacheKey 22 | completionCacheKey <- completionCacheKey + 1 23 | completionCache.Add(key, completionItem) 24 | key 25 | 26 | let mutable completionCacheCount = 0 27 | 28 | let mutable private completionPartialCache : (CompletionParams * CompletionItem list) option = None 29 | let completionResolveItem (gameObj: IGame option) (item: CompletionItem) = 30 | async { 31 | logInfo "Completion resolve" 32 | let item = 33 | match item.data with 34 | | JsonValue.Number key -> completionCache.GetValueOrDefault(key |> int, item) 35 | | _ -> item 36 | return 37 | match gameObj with 38 | | Some game -> 39 | let allEffects = 40 | game.ScriptedEffects() @ game.ScriptedTriggers() 41 | 42 | let hovered = 43 | allEffects 44 | |> List.tryFind (fun e -> e.Name.GetString() = item.label) 45 | 46 | match hovered with 47 | | Some effect -> 48 | match effect with 49 | | :? DocEffect as de -> 50 | let desc = "_" + de.Desc.Replace("_", "\\_") + "_" 51 | 52 | let scopes = 53 | "Supports scopes: " 54 | + String.Join(", ", de.Scopes |> List.map (fun f -> f.ToString())) 55 | 56 | let usage = de.Usage 57 | 58 | let content = 59 | String.Join("\n***\n", [ desc; scopes; usage ]) // TODO: usageeffect.Usage]) 60 | //{item with documentation = (MarkupContent ("markdown", content))} 61 | { item with 62 | documentation = 63 | Some( 64 | { kind = MarkupKind.Markdown 65 | value = content } 66 | ) } 67 | | :? ScriptedEffect as se -> 68 | let desc = se.Name.GetString().Replace("_", "\\_") 69 | let comments = se.Comments.Replace("_", "\\_") 70 | 71 | let scopes = 72 | "Supports scopes: " 73 | + String.Join(", ", se.Scopes |> List.map (fun f -> f.ToString())) 74 | 75 | let content = 76 | String.Join("\n***\n", [ desc; comments; scopes ]) // TODO: usageeffect.Usage]) 77 | 78 | { item with 79 | documentation = 80 | Some( 81 | { kind = MarkupKind.Markdown 82 | value = content } 83 | ) } 84 | | e -> 85 | let desc = "_" + e.Name.GetString().Replace("_", "\\_") + "_" 86 | 87 | let scopes = 88 | "Supports scopes: " 89 | + String.Join(", ", e.Scopes |> List.map (fun f -> f.ToString())) 90 | 91 | let content = String.Join("\n***\n", [ desc; scopes ]) // TODO: usageeffect.Usage]) 92 | 93 | { item with 94 | documentation = 95 | Some( 96 | { kind = MarkupKind.Markdown 97 | value = content } 98 | ) } 99 | | None -> item 100 | | None -> item 101 | } 102 | 103 | 104 | 105 | let optimiseCompletion (completionList: CompletionItem list) = 106 | if completionCacheCount > 2 then 107 | completionCache.Clear() 108 | completionCacheCount <- 0 109 | else 110 | completionCacheCount <- completionCacheCount + 1 111 | 112 | match completionList.Length with 113 | | x when x > 1000 -> 114 | let sorted = 115 | completionList 116 | |> List.sortBy (fun c -> c.sortText) 117 | 118 | let first = sorted |> List.take(1000) 119 | 120 | let rest = 121 | sorted |> List.skip(1000) 122 | |> List.take (min 1000 (x - 1000)) 123 | |> List.map 124 | (fun item -> 125 | let key = addToCache item 126 | 127 | { item with 128 | documentation = None 129 | detail = None 130 | data = JsonValue.Number (decimal key) }) 131 | 132 | first @ rest 133 | | _ -> completionList 134 | 135 | let checkPartialCompletionCache (p : CompletionParams) genItems = 136 | match p.context ,completionPartialCache, p.textDocument, p.position with 137 | | Some { triggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions }, Some (c, res), td, pos 138 | when c.position.line = pos.line 139 | && c.textDocument.uri = td.uri -> 140 | res 141 | | _ -> 142 | let items = genItems() 143 | completionPartialCache <- Some (p, items) 144 | items 145 | 146 | let completionCallLSP (game : IGame) (p : CompletionParams) docs debugMode filetext position = 147 | 148 | let path = 149 | let u = p.textDocument.uri 150 | 151 | if 152 | RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 153 | && u.LocalPath.StartsWith "/" 154 | then 155 | u.LocalPath.Substring(1) 156 | else 157 | u.LocalPath 158 | 159 | let comp = 160 | game.Complete 161 | position 162 | path 163 | filetext 164 | 165 | 166 | // logInfo $"completion {prefixSoFar}" 167 | // let extraKeywords = ["yes"; "no";] 168 | // let eventIDs = game.References.EventIDs 169 | // let names = eventIDs @ game.References.TriggerNames @ game.References.EffectNames @ game.References.ModifierNames @ game.References.ScopeNames @ extraKeywords 170 | let convertKind (x: CompletionCategory) = 171 | match x with 172 | | CompletionCategory.Link -> (true, CompletionItemKind.Method) 173 | | CompletionCategory.Value -> (false, CompletionItemKind.Value) 174 | | CompletionCategory.Global -> (false, CompletionItemKind.Constant) 175 | | CompletionCategory.Variable -> (false, CompletionItemKind.Variable) 176 | | _ -> (false, CompletionItemKind.Function) 177 | 178 | let createLabel = 179 | (fun l score -> if debugMode then $"{l}({score})" else l) 180 | 181 | /// Wrap in quotes if it contains spaces 182 | let createInsertText (s : string) = 183 | if s.Contains " " && not (s.StartsWith("\"")) && not (s.EndsWith("\"")) 184 | then Some $"\"{s}\"" 185 | else Some s 186 | 187 | let items = 188 | comp 189 | |> List.map 190 | (function 191 | | CompletionResponse.Simple (e, Some score, kind) -> 192 | { defaultCompletionItemKind (convertKind kind) with 193 | label = createLabel e score 194 | insertText = createInsertText e 195 | sortText = Some((maxCompletionScore - score).ToString()) } 196 | | CompletionResponse.Simple (e, None, kind) -> 197 | { defaultCompletionItemKind (convertKind kind) with 198 | label = e 199 | insertText = createInsertText e 200 | sortText = Some(maxCompletionScore.ToString()) } 201 | | CompletionResponse.Detailed (l, d, Some score, kind) -> 202 | { defaultCompletionItemKind (convertKind kind) with 203 | label = createLabel l score 204 | insertText = createInsertText l 205 | documentation = 206 | d 207 | |> Option.map 208 | (fun d -> 209 | { kind = MarkupKind.Markdown 210 | value = d }) 211 | sortText = Some((maxCompletionScore - score).ToString()) } 212 | | CompletionResponse.Detailed (l, d, None, kind) -> 213 | { defaultCompletionItemKind (convertKind kind) with 214 | label = l 215 | insertText = createInsertText l 216 | documentation = 217 | d 218 | |> Option.map 219 | (fun d -> 220 | { kind = MarkupKind.Markdown 221 | value = d }) } 222 | | CompletionResponse.Snippet (l, e, d, Some score, kind) -> 223 | { defaultCompletionItemKind (convertKind kind) with 224 | label = createLabel l score 225 | insertText = Some e 226 | insertTextFormat = Some InsertTextFormat.Snippet 227 | documentation = 228 | d 229 | |> Option.map 230 | (fun d -> 231 | { kind = MarkupKind.Markdown 232 | value = d }) 233 | sortText = Some((maxCompletionScore - score).ToString()) } 234 | | CompletionResponse.Snippet (l, e, d, None, kind) -> 235 | { defaultCompletionItemKind (convertKind kind) with 236 | label = l 237 | insertText = Some e 238 | insertTextFormat = Some InsertTextFormat.Snippet 239 | documentation = 240 | d 241 | |> Option.map 242 | (fun d -> 243 | { kind = MarkupKind.Markdown 244 | value = d }) }) 245 | items 246 | let completion (gameObj: IGame option) (p: CompletionParams) (docs: DocumentStore) (debugMode: bool) = 247 | match gameObj with 248 | | Some game -> 249 | // match experimental_completion with 250 | // |true -> 251 | 252 | // let variables = game.References.ScriptVariableNames |> List.map (fun v -> {defaultCompletionItem with label = v; kind = Some CompletionItemKind.Variable }) 253 | // logInfo (sprintf "completion prefix %A %A" prefixSoFar (items |> List.map (fun x -> x.label))) 254 | 255 | // let stopwatch = System.Diagnostics.Stopwatch.StartNew() 256 | // stopwatch.Start() 257 | let position = 258 | Pos.fromZ p.position.line p.position.character // |> (fun p -> Pos.fromZ) 259 | let filetext = (docs.GetText(FileInfo(p.textDocument.uri.LocalPath)) |> Option.defaultValue "") 260 | 261 | let items = checkPartialCompletionCache p (fun () -> completionCallLSP game p docs debugMode filetext position) 262 | 263 | // logInfo $"completion items time %i{stopwatch.ElapsedMilliseconds}ms" 264 | let split = filetext.Split('\n') 265 | let targetLine = split.[position.Line - 1] 266 | let textBeforeCursor = targetLine.Remove (position.Column) 267 | let prefixSoFar = 268 | match textBeforeCursor.Split([||]) |> Array.tryLast with 269 | | Some lastWord when not (String.IsNullOrWhiteSpace lastWord) -> lastWord.Split('.') |> Array.last |> Some 270 | | _ -> None 271 | 272 | let partialReturn = items |> List.length > 2000 273 | let filtered = 274 | match prefixSoFar, partialReturn with 275 | | None, _ -> items 276 | | _, false -> items 277 | | Some prefix, true -> items |> List.filter (fun i -> i.label.Contains(prefix, StringComparison.OrdinalIgnoreCase)) 278 | let deduped = 279 | filtered 280 | |> List.distinctBy (fun i -> (i.label, i.documentation)) 281 | |> List.filter (fun i -> not (i.label.StartsWith("$", StringComparison.OrdinalIgnoreCase))) 282 | let optimised = optimiseCompletion deduped 283 | // logInfo $"completion mid %A{prefixSoFar} %A{deduped.Head.sortText} %A{deduped.Head.label}" 284 | 285 | // let docLength = 286 | // optimised 287 | // |> List.sumBy 288 | // (fun i -> 289 | // if i.documentation.IsSome then 290 | // i.documentation.Value.value.Length 291 | // else 292 | // 0) 293 | // 294 | // let labelLength = 295 | // optimised |> List.sumBy (fun i -> i.label.Length) 296 | // 297 | // logInfo $"Completion items: %i{deduped |> List.length} %i{optimised |> List.length} %i{stopwatch.ElapsedMilliseconds}ms" 298 | 299 | // let items = 300 | // [ { defaultCompletionItem with 301 | // label = "test" 302 | // insertText = Some "test ${1|yes,no,{ test = true }|}" 303 | // insertTextFormat = Some InsertTextFormat.Snippet } ] 304 | 305 | Some 306 | { isIncomplete = partialReturn 307 | items = optimised } 308 | // |false -> 309 | // let extraKeywords = ["yes"; "no";] 310 | // let eventIDs = game.References.EventIDs 311 | // let names = eventIDs @ game.References.TriggerNames @ game.References.EffectNames @ game.References.ModifierNames @ game.References.ScopeNames @ extraKeywords 312 | // let variables = game.References.ScriptVariableNames |> List.map (fun v -> {defaultCompletionItem with label = v; kind = Some CompletionItemKind.Variable }) 313 | // let items = names |> List.map (fun n -> {defaultCompletionItem with label = n}) 314 | // Some {isIncomplete = false; items = items @ variables} 315 | | None -> None 316 | -------------------------------------------------------------------------------- /src/Main/Git.fs: -------------------------------------------------------------------------------- 1 | module Main.Git 2 | open LibGit2Sharp 3 | open System.Text 4 | open CWTools.Common 5 | open System.IO 6 | open System.Linq 7 | open CWTools.Utilities.Utils 8 | 9 | let rec initOrUpdateRules repoPath gameCacheDir stable first = 10 | if Directory.Exists gameCacheDir then () else Directory.CreateDirectory gameCacheDir |> ignore 11 | try 12 | let isRepo = Repository.IsValid gameCacheDir 13 | if isRepo then () else Repository.Clone(repoPath, gameCacheDir) |> ignore 14 | let git = new Repository(gameCacheDir) 15 | let remote = git.Network.Remotes.["origin"] 16 | let refSpecs = remote.FetchRefSpecs.Select((fun x -> x.Specification)) 17 | Commands.Fetch(git, remote.Name, refSpecs, null, "") 18 | let currentHash = git.Head.Tip.Sha 19 | logInfo (sprintf "cwtools current rules version: %A" currentHash) 20 | match stable with 21 | |true -> 22 | let describeOptions = DescribeOptions() 23 | describeOptions.Strategy <- DescribeStrategy.Tags 24 | describeOptions.MinimumCommitIdAbbreviatedSize <- 0 25 | let tag = git.Describe(git.Branches.["origin/master"].Tip, describeOptions) 26 | let checkoutOptions = CheckoutOptions() 27 | checkoutOptions.CheckoutModifiers <- CheckoutModifiers.Force 28 | Commands.Checkout(git, tag, checkoutOptions) |> ignore 29 | |false -> 30 | git.Reset(ResetMode.Hard, git.Branches.["origin/master"].Tip) 31 | let newHash = git.Head.Tip.Sha 32 | logInfo (sprintf "cwtools new rules version: %A" newHash) 33 | (newHash <> currentHash) || not isRepo, Some git.Head.Tip.Committer.When 34 | with 35 | | ex -> 36 | logError (sprintf "cwtools git error, recovering, error: %A" ex) 37 | use git = new Repository(gameCacheDir) 38 | git.Reset(ResetMode.Hard, git.Branches.["origin/master"].Tip) |> ignore 39 | if first then initOrUpdateRules repoPath gameCacheDir stable false else (false, None) 40 | 41 | 42 | // var initOrUpdateRules = function(folder : string, repoPath : string, logger : vs.OutputChannel, first? : boolean) { 43 | // const gameCacheDir = isDevDir ? context.storagePath + '/.cwtools/' + folder : context.extensionPath + '/.cwtools/' + folder 44 | // var rulesVersion = "embedded" 45 | // if (rulesChannel != "none") { 46 | // !isDevDir || fs.existsSync(context.storagePath) || fs.mkdirSync(context.storagePath) 47 | // fs.existsSync(cacheDir) || fs.mkdirSync(cacheDir) 48 | // fs.existsSync(gameCacheDir) || fs.mkdirSync(gameCacheDir) 49 | // const git = simplegit(gameCacheDir) 50 | // let ret = git.checkIsRepo() 51 | // .then(isRepo => !isRepo && git.clone(repoPath, gameCacheDir)) 52 | // .then(() => git.fetch()) 53 | // .then(() => git.log()) 54 | // .then((log) => { logger.appendLine("cwtools current rules version: " + log.latest.hash); return log.latest.hash }) 55 | // .then((prevHash : string) => { return Promise.all([prevHash, git.checkout("master")]) }) 56 | // //@ts-ignore 57 | // .then(function ([prevHash, _]) { return Promise.all([prevHash, rulesChannel == "latest" ? git.reset(["--hard", "origin/master"]) : git.checkoutLatestTag()])} ) 58 | // .then(function ([prevHash, _]) { return Promise.all([prevHash, git.log()]) }) 59 | // .then(function ([prevHash, log]) { return log.latest.hash == prevHash ? undefined : log.latest.date }) 60 | // .catch(() => { logger.appendLine("cwtools git error, recovering"); git.reset(["--hard", "origin/master"]); first && initOrUpdateRules(folder, repoPath, logger, false) }) 61 | // return ret; 62 | // } 63 | // else { 64 | // return Promise.resolve() 65 | // } 66 | // } 67 | -------------------------------------------------------------------------------- /src/Main/LanguageServerFeatures.fs: -------------------------------------------------------------------------------- 1 | namespace Main.Lang 2 | open LSP.Json.Ser 3 | open LSP 4 | open LSP.Types 5 | open System 6 | open System.Runtime.InteropServices 7 | open CWTools.Utilities.Position 8 | open CWTools.Utilities.StringResource 9 | open CWTools.Games 10 | open System.IO 11 | open CWTools.Localisation 12 | open LSP.Types 13 | 14 | module LanguageServerFeatures = 15 | let convRangeToLSPRange (range : range) = 16 | { 17 | start = { 18 | line = (int range.StartLine - 1) 19 | character = (int range.StartColumn) 20 | } 21 | ``end`` = { 22 | line = (int range.EndLine - 1) 23 | character = (int range.EndColumn) 24 | } 25 | } 26 | 27 | let getWordAtPos (client : ILanguageClient) pos doc = 28 | let pjson = pos |> (serializerFactory defaultJsonWriteOptions) 29 | let ujson = doc |> (serializerFactory defaultJsonWriteOptions) 30 | let json = serializerFactory defaultJsonWriteOptions ({ position = pos; uri = doc }) 31 | client.CustomRequest("getWordRangeAtPosition", json) 32 | 33 | let getPathFromDoc (doc : Uri) = 34 | let u = doc 35 | if System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && u.LocalPath.StartsWith "/" 36 | then u.LocalPath.Substring(1) 37 | else u.LocalPath 38 | 39 | let lochoverFromInfo (localisation : (string * Entry) list) (infoOption : CWTools.Games.SymbolInformation option) (word : string) = 40 | let locToText (loc : SymbolLocalisationInfo) = 41 | let locdesc = localisation |> List.tryPick (fun (k, v) -> if k = loc.value then Some v.desc else None) 42 | |> Option.defaultValue "" 43 | "|" + loc.key.Trim('\"') + "|" + locdesc.Trim('\"') + "|" 44 | match infoOption with 45 | |Some info -> 46 | match info.localisation with 47 | |[] -> localisation |> List.tryPick (fun (k, v) -> if k = word then Some v.desc else None) 48 | |[h] -> localisation |> List.tryPick (fun (k, v) -> if k = h.value then Some v.desc else None) 49 | |h::t -> 50 | let head = locToText h 51 | let tail = t |> List.map locToText 52 | Some ((head :: "|:---|:---|" :: tail) |> (fun s -> String.Join("\n", s))) 53 | |None -> localisation |> List.tryPick (fun (k, v) -> if k = word then Some v.desc else None) 54 | 55 | let docstringFromInfo (infoOption : CWTools.Games.SymbolInformation option) = 56 | match infoOption with 57 | | Some info -> 58 | let ruleDesc = info.ruleDescription 59 | let scopes = 60 | match info.ruleRequiredScopes with 61 | | [] -> None 62 | | x -> Some( "Supports scopes: " + String.Join(", ", info.ruleRequiredScopes)) 63 | Some (String.Join("\n***\n",[|ruleDesc; scopes|] |> Array.choose id)) 64 | | None -> None 65 | 66 | 67 | let hoverDocument (eu4GameObj) (hoi4GameObj) (stlGameObj) (ck2GameObj) (irGameObj) (vic2GameObj) (ck3GameObj) (vic3GameObj: 'h option) (customGameObj) (client : ILanguageClient) (docs : DocumentStore) (doc : Uri) (pos: LSP.Types.Position) = 68 | async { 69 | let! word = getWordAtPos client pos doc 70 | let unescapedword = word.ToString().Replace("\\\"", "\"").Trim('"') 71 | let position = Pos.fromZ pos.line pos.character 72 | let path = getPathFromDoc doc 73 | let hoverFunction (game : IGame<_>) = 74 | let symbolInfo = game.InfoAtPos position path (docs.GetText (FileInfo (doc.LocalPath)) |> Option.defaultValue "") 75 | let scopeContext = game.ScopesAtPos position (path) (docs.GetText (FileInfo (doc.LocalPath)) |> Option.defaultValue "") 76 | let allEffects = game.ScriptedEffects() @ game.ScriptedTriggers() 77 | eprintfn "Looking for effect %s in the %i effects loaded" (word.ToString()) (allEffects.Length) 78 | let hovered = allEffects |> List.tryFind (fun e -> e.Name.GetString() = unescapedword) 79 | let lochover = lochoverFromInfo (game.References().Localisation) symbolInfo unescapedword 80 | let scopesExtra = 81 | if scopeContext.IsNone then "" else 82 | let scopes = scopeContext.Value 83 | let header = "| Context | Scope |\n| ----- | -----|\n" 84 | let root = sprintf "| ROOT | %s |\n" (scopes.Root.ToString()) 85 | let prevs = scopes.Scopes |> List.mapi (fun i s -> "| " + (if i = 0 then "THIS" else (String.replicate (i) "PREV")) + " | " + (s.ToString()) + " |\n") |> String.concat "" 86 | let froms = scopes.From |> List.mapi (fun i s -> "| " + (String.replicate (i+1) "FROM") + " | " + (s.ToString()) + " |\n") |> String.concat "" 87 | header + root + prevs + froms 88 | 89 | let effect = 90 | hovered |> Option.map (fun e -> 91 | match e with 92 | | :? CWTools.Common.DocEffect as de -> 93 | let scopes = String.Join(", ", de.Scopes |> List.map (fun f -> f.ToString())) 94 | let desc = de.Desc.Replace("_", "\\_").Trim() |> (fun s -> if s = "" then "" else "_"+s+"_" ) 95 | (String.Join("\n***\n",[desc; "Supports scopes: " + scopes]) )// TODO: usageeffect.Usage]) 96 | | e -> 97 | let scopes = String.Join(", ", e.Scopes |> List.map (fun f -> f.ToString())) 98 | let name = e.Name.GetString().Replace("_","\\_").Trim() 99 | (String.Join("\n***\n",["_"+name+"_"; "Supports scopes: " + scopes])) // TODO: usageeffect.Usage]) 100 | ) 101 | let docStringOrEffect = Option.orElse (docstringFromInfo symbolInfo) effect 102 | let text = 103 | [|docStringOrEffect; lochover; Some scopesExtra|] 104 | |> Array.choose id 105 | |> (fun a -> String.Join("\n\n***\n\n", a)) 106 | 107 | match text with 108 | | "" -> {contents = MarkupContent ("markdown", ""); range = None} 109 | | text -> {contents = MarkupContent ("markdown", text); range = None} 110 | // match hovered, lochover, docstringFromInfo symbolInfo with 111 | // |Some effect, _, _ -> 112 | // match effect with 113 | // | :? CWTools.Common.DocEffect<'a> as de -> 114 | // let scopes = String.Join(", ", de.Scopes |> List.map (fun f -> f.ToString())) 115 | // let desc = de.Desc.Replace("_", "\\_").Trim() |> (fun s -> if s = "" then "" else "_"+s+"_" ) 116 | // let content = String.Join("\n***\n",[desc; "Supports scopes: " + scopes; scopesExtra]) // TODO: usageeffect.Usage]) 117 | // {contents = (MarkupContent ("markdown", content)) ; range = None} 118 | // | e -> 119 | // let scopes = String.Join(", ", e.Scopes |> List.map (fun f -> f.ToString())) 120 | // let name = e.Name.Replace("_","\\_").Trim() 121 | // let content = String.Join("\n***\n",["_"+name+"_"; "Supports scopes: " + scopes; scopesExtra]) // TODO: usageeffect.Usage]) 122 | // {contents = (MarkupContent ("markdown", content)) ; range = None} 123 | // |None, Some loc, _-> 124 | // {contents = MarkupContent ("markdown", loc + "\n\n***\n\n" + scopesExtra); range = None} 125 | // |None, None, Some ruleDesc -> 126 | // {contents = MarkupContent ("markdown", ruleDesc + "\n\n***\n\n" + scopesExtra); range = None} 127 | // |None, None, None -> 128 | // {contents = MarkupContent ("markdown", scopesExtra); range = None} 129 | return 130 | match stlGameObj, hoi4GameObj, eu4GameObj, ck2GameObj, irGameObj, vic2GameObj, ck3GameObj, vic3GameObj, customGameObj with 131 | |Some game, _, _, _, _, _, _, _, _ -> hoverFunction game 132 | |_, Some game, _, _, _, _, _, _, _ -> hoverFunction game 133 | |_, _, Some game, _, _, _, _, _, _ -> hoverFunction game 134 | |_, _, _, Some game, _, _, _, _, _ -> hoverFunction game 135 | |_, _, _, _, Some game, _, _, _, _ -> hoverFunction game 136 | |_, _, _, _, _, Some game, _, _, _ -> hoverFunction game 137 | |_, _, _, _, _, _, Some game, _, _ -> hoverFunction game 138 | |_, _, _, _, _, _, _, Some game, _ -> hoverFunction game 139 | |_, _, _, _, _, _, _, _, Some game -> hoverFunction game 140 | |_ -> {contents = MarkupContent ("markdown", ""); range = None} 141 | 142 | } 143 | 144 | let pretriggerForFile (client : ILanguageClient) (game : IGame) (docs : DocumentStore) (filename) = 145 | let getEventChanges (deletes, insertPos, insertText) = 146 | let removes = deletes |> Seq.map (fun delRange -> { range = convRangeToLSPRange delRange; newText = "" }) |> List.ofSeq 147 | let add = { range = convRangeToLSPRange (mkRange filename insertPos insertPos); newText = insertText } 148 | add :: removes 149 | let getFileText (filename) = File.ReadAllText filename 150 | let edits = game.GetCodeEdits (filename) ((docs.GetText (FileInfo(filename)) |> Option.defaultValue (getFileText filename))) 151 | let combined = edits |> Option.defaultValue [] |> List.collect (getEventChanges) 152 | match combined with 153 | |[] -> () 154 | |textedits -> 155 | let fileInfo = FileInfo(filename) 156 | let version = docs.GetVersion (fileInfo) |> Option.defaultValue 0 157 | let docIdentifier = { uri = Uri(filename); version = version} 158 | let changes = { textDocument = docIdentifier; edits = textedits} 159 | let docChanges = { documentChanges = [changes] } 160 | client.ApplyWorkspaceEdit { label = Some (sprintf "Pretriggers %s" fileInfo.Name); edit = docChanges } |> Async.RunSynchronously |> ignore 161 | -------------------------------------------------------------------------------- /src/Main/Main.Linux.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | CWTools Server 4 | Exe 5 | net6.0 6 | true 7 | false 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Main/Main.Linux.fsproj.paket.references: -------------------------------------------------------------------------------- 1 | System.Text.Encoding.CodePages 2 | Fsharp.Data 3 | FsPickler 4 | FParsec 5 | 6 | group LinuxLibGit 7 | LibGit2Sharp 8 | LibGit2Sharp.NativeBinaries -------------------------------------------------------------------------------- /src/Main/Main.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | CWTools Server 4 | Exe 5 | net8.0 6 | false 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Main/Main.fsproj.paket.references: -------------------------------------------------------------------------------- 1 | System.Text.Encoding.CodePages 2 | Fsharp.Data 3 | FsPickler 4 | FParsec 5 | 6 | group WindowsLibGit 7 | LibGit2Sharp 8 | LibGit2Sharp.NativeBinaries -------------------------------------------------------------------------------- /src/Main/ProjectManager.fs: -------------------------------------------------------------------------------- 1 | namespace Main 2 | 3 | open LSP 4 | open System 5 | open System.IO 6 | open System.Collections.Generic 7 | open System.Net 8 | open System.Xml 9 | open FSharp.Data 10 | open FSharp.Data.JsonExtensions 11 | open Microsoft.VisualBasic.CompilerServices 12 | 13 | type CompilerOptions = { 14 | sources: list 15 | projectReferences: list 16 | references: list 17 | } 18 | 19 | module ProjectManagerUtils = 20 | type Dependency = { 21 | _type: string 22 | compile: list 23 | } 24 | 25 | type Library = { 26 | path: string 27 | } 28 | 29 | type ProjectAssets = { 30 | targets: Map> 31 | libraries: Map 32 | packageFolders: list 33 | } 34 | 35 | let private fixPath (path: string): string = 36 | path.Replace('\\', Path.DirectorySeparatorChar) 37 | let keys (record: JsonValue): list = 38 | List.ofSeq(seq { 39 | for key, _ in record.Properties do 40 | yield key 41 | }) 42 | let private parseDependency (info: JsonValue): Dependency = 43 | let compile = info.TryGetProperty("compile") |> Option.map keys 44 | { 45 | _type = info?``type``.AsString() 46 | compile = defaultArg compile [] 47 | } 48 | let private parseDependencies (dependencies: JsonValue): Map = 49 | Map.ofSeq(seq { 50 | for name, info in dependencies.Properties do 51 | yield name, parseDependency info 52 | }) 53 | let private parseTargets (targets: JsonValue): Map> = 54 | Map.ofSeq(seq { 55 | for target, dependencies in targets.Properties do 56 | yield target, parseDependencies dependencies 57 | }) 58 | let private parseLibrary (library: JsonValue): Library = 59 | { 60 | path = library?path.AsString() 61 | } 62 | let private parseLibraries (libraries: JsonValue): Map = 63 | Map.ofSeq(seq { 64 | for dependency, info in libraries.Properties do 65 | yield dependency, parseLibrary info 66 | }) 67 | let parseAssetsJson (text: string): ProjectAssets = 68 | let json = JsonValue.Parse text 69 | { 70 | targets = parseTargets json?targets 71 | libraries = parseLibraries json?libraries 72 | packageFolders = keys json?packageFolders 73 | } 74 | let private parseAssets (path: FileInfo): ProjectAssets = 75 | let text = File.ReadAllText path.FullName 76 | parseAssetsJson text 77 | // Find all dlls in project.assets.json 78 | let private references (assets: ProjectAssets): list = 79 | let resolveInPackageFolders (dependencyPath: string): option = 80 | seq { 81 | for packageFolder in assets.packageFolders do 82 | let absolutePath = Path.Combine(packageFolder, dependencyPath) 83 | if File.Exists absolutePath then 84 | yield FileInfo(absolutePath) 85 | } |> Seq.tryHead 86 | let resolveInLibrary (library: string) (dll: string): option = 87 | let libraryPath = assets.libraries.[library].path 88 | let dependencyPath = Path.Combine(libraryPath, dll) |> fixPath 89 | resolveInPackageFolders dependencyPath 90 | List.ofSeq(seq { 91 | for target in assets.targets do 92 | for dependency in target.Value do 93 | if dependency.Value._type = "package" && Map.containsKey dependency.Key assets.libraries then 94 | for dll in dependency.Value.compile do 95 | let resolved = resolveInLibrary dependency.Key dll 96 | if resolved.IsSome then 97 | yield resolved.Value 98 | else 99 | let packageFolders = String.concat ", " assets.packageFolders 100 | eprintfn "Couldn't find %s in %s" dll packageFolders 101 | }) 102 | // Parse fsproj 103 | let private parseProject (fsproj: FileInfo): XmlElement = 104 | let text = File.ReadAllText fsproj.FullName 105 | let doc = XmlDocument() 106 | doc.LoadXml text 107 | doc.DocumentElement 108 | // Parse fsproj and fsproj/../obj/project.assets.json 109 | let parseBoth (path: FileInfo): CompilerOptions = 110 | let project = parseProject path 111 | // Find all elements in fsproj 112 | let sources (fsproj: XmlNode): list = 113 | List.ofSeq(seq { 114 | for n in fsproj.SelectNodes "//Compile[@Include]" do 115 | let relativePath = n.Attributes.["Include"].Value |> fixPath 116 | let absolutePath = Path.Combine(path.DirectoryName, relativePath) 117 | yield FileInfo(absolutePath) 118 | }) 119 | // Find all elements in fsproj 120 | let projectReferences (fsproj: XmlNode): list = 121 | List.ofSeq(seq { 122 | for n in fsproj.SelectNodes "//ProjectReference[@Include]" do 123 | let relativePath = n.Attributes.["Include"].Value |> fixPath 124 | let absolutePath = Path.Combine(path.DirectoryName, relativePath) 125 | yield FileInfo(absolutePath) 126 | }) 127 | let assetsFile = Path.Combine(path.DirectoryName, "obj", "project.assets.json") |> FileInfo 128 | if assetsFile.Exists then 129 | let assets = parseAssets assetsFile 130 | { 131 | sources = sources project 132 | projectReferences = projectReferences project 133 | references = references assets 134 | } 135 | else 136 | { 137 | sources = sources project 138 | projectReferences = projectReferences project 139 | references = [] 140 | } 141 | 142 | open ProjectManagerUtils 143 | 144 | type ProjectManager() = 145 | let cache = new Dictionary() 146 | let addToCache (projectFile: FileInfo): CompilerOptions = 147 | let parsed = parseBoth projectFile 148 | cache.[projectFile.Directory] <- parsed 149 | parsed 150 | // Scan the parent directories looking for a file *.fsproj 151 | let findProjectFileInParents (sourceFile: FileInfo): option = 152 | seq { 153 | let mutable dir = sourceFile.Directory 154 | while dir <> dir.Root do 155 | for proj in dir.GetFiles("*.fsproj") do 156 | yield proj 157 | dir <- dir.Parent 158 | } |> Seq.tryHead 159 | let tryFindAndCache (sourceFile: FileInfo): option = 160 | match findProjectFileInParents sourceFile with 161 | | None -> 162 | eprintfn "No project file for %s" sourceFile.Name 163 | None 164 | | Some projectFile -> 165 | eprintfn "Found project file %s for %s" projectFile.FullName sourceFile.Name 166 | Some (addToCache projectFile) 167 | member this.UpdateProjectFile(project: Uri): unit = 168 | let file = FileInfo(project.AbsolutePath) 169 | addToCache file |> ignore 170 | member this.FindProjectOptions(sourceFile: Uri): option = 171 | let file = FileInfo(sourceFile.AbsolutePath) 172 | match tryFindAndCache file with 173 | | Some cachedProject -> Some cachedProject 174 | | None -> tryFindAndCache file -------------------------------------------------------------------------------- /src/Main/rootDescriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "client/webview/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noUnusedLocals": false, 4 | "noUnusedParameters": true, 5 | "noImplicitAny": true, 6 | "noImplicitReturns": true, 7 | "target": "es2020", 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "rootDir": "client", 11 | "outDir": "out/client", 12 | "lib": [ "es2020", "dom" ], 13 | "sourceMap": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": [ 17 | "client" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "server", 22 | "paket-files", 23 | ".vscode-test" 24 | ] 25 | } -------------------------------------------------------------------------------- /tsconfig.webview.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "target": "es2020", 5 | // "outFile": "out/client/webview/graph.js", 6 | "lib": ["es2020", "dom"], 7 | 8 | }, 9 | "extends": "./tsconfig.json", 10 | "exclude": [ 11 | "client/extension/*.ts", 12 | "client/test/*.ts" 13 | ] 14 | } --------------------------------------------------------------------------------