diff --git a/.github/ghprcomment.yml b/.github/ghprcomment.yml index 9f740d626f8..f42ddc26b33 100644 --- a/.github/ghprcomment.yml +++ b/.github/ghprcomment.yml @@ -34,3 +34,8 @@ message: | While the PR was in progress, a new version of JabRef has been released. You have to merge `upstream/main` and move your entry in `CHANGELOG.md` up to the section `## [Unreleased]`. +- jobName: 'Unit tests' + message: | + JUnit tests are failing. In the area "Some checks were not successful", locate "Tests / Unit tests (pull_request)" and click on "Details". This brings you to the test output. + + You can then run these tests in IntelliJ to reproduce the failing tests locally. We offer a quick test running howto in the section [Final build system checks](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.html#final-build-system-checks) in our setup guide. diff --git a/.github/workflows/on-review-submitted.yml b/.github/workflows/on-review-submitted.yml deleted file mode 100644 index f82cfd9a304..00000000000 --- a/.github/workflows/on-review-submitted.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: On reviewed PR - -on: - pull_request_review: - types: - - submitted - -jobs: - add-label-changes-required: - runs-on: ubuntu-latest - steps: - - run: | - gh pr edit "$PR_URL" --remove-label "status: ready-for-review" - gh pr edit "$PR_URL" --add-label "status: changes required" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER}} diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index e3a82fa08ce..c362f5e250a 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -34,15 +34,24 @@ jobs: PR_NUMBER=$(cat pr_number.txt) echo "Read PR number $PR_NUMBER" echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - - name: Checkout + - name: Determine owner if: ${{ steps.read-pr_number.outputs.pr_number != '' }} + id: owner + run: | + owner=$(gh pr view $pr_number --json headRepositoryOwner --jq '.headRepositoryOwner') + echo "Got owner $owner" + echo owner=$owner >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + - name: Checkout + if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.owner.owner != 'JabRef') }} uses: actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' token: ${{ secrets.GITHUB_TOKEN }} - name: jbang - if: ${{ steps.read-pr_number.outputs.pr_number != '' }} + if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.owner.owner != 'JabRef') }} uses: jbangdev/jbang-action@v0.119.0 with: script: ghprcomment@koppor/ghprcomment diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dec509bd1e..31a8c063e16 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -380,7 +380,7 @@ jobs: uses: actions/github-script@v7 with: script: | - core.setFailed('Pull requests should come from a branch other than "main"\n\n👉 Please read https://devdocs.jabref.org/contributing again carefully. 👈') + core.setFailed('Pull requests should come from a branch other than "main"\n\n👉 Please read [the CONTRIBUTING guide](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md#contributing) carefully again. 👈') upload-pr-number: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 789d8ec6968..85fa26fb451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) -- We added probable search hits instead of exact matches. Sorting by hit score can be done by the new score table column. [#11542](https://github.com/JabRef/jabref/pull/11542) - We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) - When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) - We added an AI-based chat for entries with linked PDF files. [#11430](https://github.com/JabRef/jabref/pull/11430) @@ -31,9 +30,12 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735) - When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614) - We added support for drag'n'drop on an entry in the maintable to an external application to get the entry preview dropped. [#11846](https://github.com/JabRef/jabref/pull/11846) +- We added the functionality to double click on a [LaTeX citation](https://docs.jabref.org/advanced/entryeditor/latex-citations) to jump to the respective line in the LaTeX editor. [#11996](https://github.com/JabRef/jabref/issues/11996) - We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658) - We added a setting which always adds the literal "Cited on pages" text before each JStyle citation. [#11691](https://github.com/JabRef/jabref/pull/11732) - We added a new plain citation parser that uses LLMs. [#11825](https://github.com/JabRef/jabref/issues/11825) +- We added support for modifier keys when dropping a file on an entry in the main table. [#12001](https://github.com/JabRef/jabref/pull/12001) +- We added an importer for SSRN URLs. [#12021](https://github.com/JabRef/jabref/pull/12021) - We added a compare button to the duplicates in the citation relations tab to open the "Possible duplicate entries" window. [#11192](https://github.com/JabRef/jabref/issues/11192) - We added automatic browser extension install on Windows for Chrome and Edge. [#6076](https://github.com/JabRef/jabref/issues/6076) - We added a search bar for filtering keyboard shortcuts. [#11686](https://github.com/JabRef/jabref/issues/11686) @@ -41,8 +43,6 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Changed -- The search syntax is changed to [Apache Lucene syntax](https://lucene.apache.org/core/9_11_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Overview) (also to be similar to the [online search syntax](https://docs.jabref.org/collect/import-using-online-bibliographic-database#search-syntax)). [#11542](https://github.com/JabRef/jabref/pull/11542/) -- When searching using a regular expression, one needs to enclose the search string in `/`. [#11542](https://github.com/JabRef/jabref/pull/11542/) - A search in "any" fields ignores the [groups](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups). [#7996](https://github.com/JabRef/jabref/issues/7996) - When a communication error with an [online service](https://docs.jabref.org/collect/import-using-online-bibliographic-database) occurs, JabRef displays the HTTP error. [#11223](https://github.com/JabRef/jabref/issues/11223) - The Pubmed/Medline Plain importer now imports the PMID field as well [#11488](https://github.com/JabRef/jabref/issues/11488) @@ -50,6 +50,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - JabRef respects the [configuration for storing files relative to the .bib file](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files) in more cases. [#11492](https://github.com/JabRef/jabref/pull/11492) - JabRef does not show finished background tasks in the status bar popup. [#11821](https://github.com/JabRef/jabref/pull/11821) - We enhanced the indexing speed. [#11502](https://github.com/JabRef/jabref/pull/11502) +- When dropping a file into the main table, after copy or move, the file is now put in the [configured directory and renamed according to the configured patterns](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#filename-format-and-file-directory-pattern). [#12001](https://github.com/JabRef/jabref/pull/12001) - ⚠️ Renamed command line parameters `embeddBibfileInPdf` to `embedBibFileInPdf`, `writeMetadatatoPdf` to `writeMetadataToPdf`, and `writeXMPtoPdf` to `writeXmpToPdf`. [#11575](https://github.com/JabRef/jabref/pull/11575) - The browse button for a Custom theme now opens in the directory of the current used CSS file. [#11597](https://github.com/JabRef/jabref/pull/11597) - The browse button for a Custom exporter now opens in the directory of the current used exporter file. [#11717](https://github.com/JabRef/jabref/pull/11717) @@ -97,11 +98,12 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where recently opened files were not displayed in the main menu properly. [#9042](https://github.com/JabRef/jabref/issues/9042) - We fixed an issue where the DOI lookup would show an error when a DOI was found for an entry. [#11850](https://github.com/JabRef/jabref/issues/11850) - We fixed an issue where Tab cannot be used to jump to next field in some single-line fields. [#11785](https://github.com/JabRef/jabref/issues/11785) +- We fixed an issue where it was not possible to select selecting content of other user's comments.[#11106](https://github.com/JabRef/jabref/issues/11106) - We fixed an issue where web search preferences "Custom API key" table modifications not discarded. [#11925](https://github.com/JabRef/jabref/issues/11925) +- We fixed an issue where trying to open a library from a failed mounted directory on Mac would cause an error. [#10548](https://github.com/JabRef/jabref/issues/10548) ### Removed -- We removed support for case-sensitive and exact search. [#11542](https://github.com/JabRef/jabref/pull/11542) - We removed the description of search strings. [#11542](https://github.com/JabRef/jabref/pull/11542) - We removed support for importing using the SilverPlatterImporter (`Record INSPEC`). [#11576](https://github.com/JabRef/jabref/pull/11576) - We removed support for automatically generating file links using the CLI (`--automaticallySetFileLinks`). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f6a209fdc5..e0efbdd98f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -130,18 +130,22 @@ We reserve the right to reject pull requests that contain little or no genuine a ### After submission of a pull request After you submitted a pull request, automated checks will run. -You will maybe see "Some checks were not successful". +You may see "Some checks were not successful". +You can click on failing checks to see more information about why they failed. Then, please look into them and handle accordingly. -Afterwards, you will receive comments on it. -Maybe, the pull request is approved immediatly, maybe changes are requested. -In case changes are requested, please implement them. +Afterwards, you will receive comments on your pull request. +The pull request may be approved immediatly, or a reviewer may request changes. +In that case, please implement the requested changes. -After changing your code, commit on the branch and push. -The pull request will update automatically. +After implementing changes, commit to the branch your pull request is *from* and push. +The pull request will automatically be updated with your changes. +Your commits will also be automatically squashed upon the pull request being accepted. -Never ever close the pull request and open a new one. -This causes much load on our side and is not the style of the GitHub open source community. +Please – **Never ever close a pull request and open a new one** - +This causes unessesary work on our side, and is not in the the style of the GitHub open source community. +You can push any changes you need to make to the branch your pull request is *from*. +These changes will be automatically added to your pull request. ### Development hints diff --git a/build.gradle b/build.gradle index 0c7558981c8..116feba772b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,9 +29,9 @@ plugins { id 'idea' - id 'org.openrewrite.rewrite' version '6.23.4' + id 'org.openrewrite.rewrite' version '6.25.1' - id "org.itsallcode.openfasttrace" version "3.0.0" + id "org.itsallcode.openfasttrace" version "3.0.1" } // Enable following for debugging @@ -238,7 +238,7 @@ dependencies { } implementation 'org.fxmisc.flowless:flowless:0.7.3' implementation 'org.fxmisc.richtext:richtextfx:0.11.3' - implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.58.0') { + implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.60.0') { exclude module: 'javax.inject' // Split package, use only jakarta.inject exclude module: 'commons-lang3' exclude group: 'org.apache.commons.validator' @@ -304,7 +304,7 @@ dependencies { // Implementation of the API implementation 'org.glassfish.jersey.core:jersey-server:3.1.9' // injection framework - implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.8' + implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.9' implementation 'org.glassfish.hk2:hk2-api:3.1.1' // testImplementation 'org.glassfish.hk2:hk2-testing:3.0.4' // implementation 'org.glassfish.hk2:hk2-testing-jersey:3.0.4' @@ -370,11 +370,14 @@ dependencies { // Even if "compileOnly" is used, IntelliJ always adds to module-info.java. To avoid issues during committing, we use "implementation" instead of "compileOnly" implementation 'io.github.adr:e-adr:2.0.0-SNAPSHOT' + implementation 'io.zonky.test:embedded-postgres:2.0.7' + implementation enforcedPlatform('io.zonky.test.postgres:embedded-postgres-binaries-bom:17.0.0') + testImplementation 'io.github.classgraph:classgraph:4.8.177' testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.3' - testImplementation 'org.mockito:mockito-core:5.14.1' + testImplementation 'org.mockito:mockito-core:5.14.2' testImplementation 'org.xmlunit:xmlunit-core:2.10.0' testImplementation 'org.xmlunit:xmlunit-matchers:2.10.0' testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.3.0' @@ -793,9 +796,13 @@ jlink { requires 'org.apache.commons.lang3' requires 'org.apache.commons.logging' requires 'org.apache.commons.text' + requires 'org.apache.commons.codec' + requires 'org.apache.commons.io' + requires 'org.apache.commons.compress' requires 'org.freedesktop.dbus' requires 'org.jsoup' requires 'org.slf4j' + requires 'org.tukaani.xz'; uses 'ai.djl.engine.EngineProvider' uses 'ai.djl.repository.RepositoryFactory' uses 'ai.djl.repository.zoo.ZooProvider' @@ -807,6 +814,7 @@ jlink { uses 'org.mariadb.jdbc.authentication.AuthenticationPlugin' uses 'org.mariadb.jdbc.credential.CredentialPlugin' uses 'org.mariadb.jdbc.tls.TlsSocketPlugin' + uses 'org.postgresql.shaded.com.ongres.stringprep.Profile' provides 'org.mariadb.jdbc.tls.TlsSocketPlugin' with 'org.mariadb.jdbc.internal.protocol.tls.DefaultTlsSocketPlugin' provides 'java.sql.Driver' with 'org.postgresql.Driver' diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index d87037495de..50edbd56165 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit d87037495de7213b896dbb6a20170387de170709 +Subproject commit 50edbd56165d8efa27be7d06ff1df5d20aa8d2c7 diff --git a/docs/decisions/0039-use-apache-velocity-as-template-engine.md b/docs/decisions/0039-use-apache-velocity-as-template-engine.md new file mode 100644 index 00000000000..5e6ca8d1072 --- /dev/null +++ b/docs/decisions/0039-use-apache-velocity-as-template-engine.md @@ -0,0 +1,110 @@ +--- +nav_order: 39 +parent: Decision Records +--- + +# Use Apache Velocity as template engine + +## Context and Problem Statement + +We need to choose a template engine for [custom export filters](https://docs.jabref.org/collaborative-work/export/customexports) and [AI features](https://github.com/JabRef/jabref/pull/11884). + +A discussion of template engines was also in [one of the JabRef repos](https://github.com/koppor/jabref/issues/392). + +A discussion was raised on StackOverflow ["Velocity vs. FreeMarker vs. Thymeleaf"](https://stackoverflow.com/q/1459426/10037342). + +## Decision Drivers + +* It should be fast. +* It should be possible to provide templates out of `String`s (required by the AI feature). +* It should have short and understandable syntax. Especially, it should work well with unset fields and empty `Optional`s. + +## Considered Options + +* Apache Velocity +* Apache FreeMarker +* Thymeleaf + +## Decision Outcome + +Chosen option: "Apache Velocity", because "Velocity's goal is to keep templates as simple as possible" ([source](https://stackoverflow.com/a/1984458/873282)). It is sufficient for our use case. +Furthermore, Apache Velocity is lightweight, and it allows to generate text output. This is a good fit for the AI feature. + +## Pros and Cons of the Options + +### Apache Velocity + +- Main page: . +- User guide: . +- Developer guide: . + +Example: + +```text +You are an AI assistant that analyses research papers. You answer questions about papers. + +Here are the papers you are analyzing: +#foreach( $entry in $entries ) +${CanonicalBibEntry.getCanonicalRepresentation($entry)} +#end +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because it has simple syntax, and it is designed for simple template workflows. +* Good, because it has a stable syntax ([source](https://stackoverflow.com/a/1984458/10037342)). +* Bad, because it is in maintenance mode. + +### Apache FreeMarker + +- Main page: . +- User guide: . +- Developer guide: . + +Example: + +```text +You are an AI assistant that analyzes research papers. You answer questions about papers. + +Here are the papers you are analyzing: +<#list entries as entry> +${CanonicalBibEntry.getCanonicalRepresentation(entry)} + +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because in active development. +* Good, because it is powerful and flexible. +* Good, because it has extensive documentation ([source](https://stackoverflow.com/a/1984458/10037342)). +* Neutral, because it has received some API and syntax changes recently ([source](https://stackoverflow.com/a/1984458/10037342)). +* Neutral, because FreeMarker is used for complex template workflow, which we do not need in JabRef. + +### Thymeleaf + +- Main page: . +- Documentation: . + +Example: + +```text +You are an AI assistant that analyzes research papers. You answer questions about papers. + +Here are the papers you are analyzing: +[# th:each="entry : ${entries}"] +[(${CanonicalBibEntry.getCanonicalRepresentation(entry)})] +[/] +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because it has [several template modes](https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#what-kind-of-templates-can-thymeleaf-process), that helps to make HTML, XML, and other templates. +* Good, because it is powerful and flexible. +* Neutral, because the API is a bit more complex than the other options. +* Bad, because the syntax is more complex than the other options. Especially for text output. + +## More Information + +As stated in [the template discussion issue](https://github.com/koppor/jabref/issues/392), we should choose a template engine, and then slowly migrate previous code and templates to the chosen engine. + + diff --git a/external-libraries.md b/external-libraries.md index 61566a72709..257aea187cb 100644 --- a/external-libraries.md +++ b/external-libraries.md @@ -350,10 +350,10 @@ License: MIT ``` ```yaml -Id:io.github.adr:e-adr -Project:EmbeddedArchitecturalDecisionRecords -URL:https://github.com/adr/e-adr/ -License:EPL-2.0 +Id: io.github.adr:e-adr +Project: EmbeddedArchitecturalDecisionRecords +URL: https://github.com/adr/e-adr/ +License: EPL-2.0 ``` ```yaml @@ -363,6 +363,13 @@ URL: https://github.com/java-diff-utils/java-diff-utils License: Apache-2.0 ``` +```yaml +Id: io.zonky.test:embedded-postgres +Project: embedded-postgres +URL: https://github.com/zonkyio/embedded-postgres +License: Apache-2.0 +``` + ```yaml Id: jakarta.annotation:jakarata.annotation-api Project: Jakarta Annotations @@ -798,6 +805,7 @@ de.undercouch:citeproc-java:3.1.0 eu.lestard:doc-annotations:0.2 info.debatty:java-string-similarity:2.0.0 io.github.java-diff-utils:java-diff-utils:4.12 +io.zonky.test:embedded-postgres:2.0.7 jakarta.activation:jakarta.activation-api:2.1.3 jakarta.annotation:jakarta.annotation-api:2.1.1 jakarta.inject:jakarta.inject-api:2.0.1 @@ -813,6 +821,7 @@ net.jodah:typetools:0.6.1 net.synedra:validatorfx:0.5.0 one.jpro.jproutils:tree-showing:0.2.2 org.antlr:antlr4-runtime:4.13.2 +org.apache.commons:commons-compress:1.27.1 org.apache.commons:commons-csv:1.11.0 org.apache.commons:commons-lang3:3.17.0 org.apache.commons:commons-text:1.12.0 @@ -889,6 +898,7 @@ org.slf4j:slf4j-api:2.0.16 org.tinylog:slf4j-tinylog:2.7.0 org.tinylog:tinylog-api:2.7.0 org.tinylog:tinylog-impl:2.7.0 +org.tukaani:xz:1.9 org.yaml:snakeyaml:2.3 pt.davidafsilva.apple:jkeychain:1.1.0 tech.units:indriya:2.2 diff --git a/src/main/antlr4/org/jabref/search/Search.g4 b/src/main/antlr4/org/jabref/search/Search.g4 index e2ee78b6c9d..0a8c28c234c 100644 --- a/src/main/antlr4/org/jabref/search/Search.g4 +++ b/src/main/antlr4/org/jabref/search/Search.g4 @@ -4,49 +4,82 @@ * These search expressions are used for searching the bibtex library. They are heavily used for search groups. */ grammar Search; +options { caseInsensitive = true; } -WS: [ \t] -> skip; // whitespace is ignored/skipped +WS: [ \t\n\r]+ -> skip; // whitespace is ignored/skipped -LPAREN:'('; -RPAREN:')'; +LPAREN: '('; +RPAREN: ')'; -EQUAL:'='; // semantically the same as CONTAINS -EEQUAL:'=='; // semantically the same as MATCHES -NEQUAL:'!='; +EQUAL: '='; // case insensitive contains, semantically the same as CONTAINS +CEQUAL: '=!'; // case sensitive contains -AND:[aA][nN][dD]; // 'and' case insensitive -OR:[oO][rR]; // 'or' case insensitive -CONTAINS:[cC][oO][nN][tT][aA][iI][nN][sS]; // 'contains' case insensitive -MATCHES:[mM][aA][tT][cC][hH][eE][sS]; // 'matches' case insensitive -NOT:[nN][oO][tT]; // 'not' case insensitive +EEQUAL: '=='; // exact match case insensitive, semantically the same as MATCHES +CEEQUAL: '==!'; // exact match case sensitive -STRING:QUOTE (~'"')* QUOTE; -QUOTE:'"'; +REQUAL: '=~'; // regex check case insensitive +CREEQUAL: '=~!'; // regex check case sensitive -FIELDTYPE:LETTER+; -// fragments are not accessible from the code, they are only for describing the grammar better -fragment LETTER : ~[ \t"()=!]; +NEQUAL: '!='; // negated case insensitive contains +NCEQUAL: '!=!'; // negated case sensitive contains +NEEQUAL: '!=='; // negated case insensitive exact match +NCEEQUAL: '!==!'; // negated case sensitive exact match -start: - expression EOF; +NREQUAL: '!=~'; // negated regex check case insensitive +NCREEQUAL: '!=~!'; // negated regex check case sensitive -// labels are used to refer to parts of the rules in the generated code later on -// label=actualThingy -expression: - LPAREN expression RPAREN #parenExpression // example: (author=miller) - | NOT expression #unaryExpression // example: not author = miller - | left=expression operator=AND right=expression #binaryExpression // example: author = miller and title = test - | left=expression operator=OR right=expression #binaryExpression // example: author = miller or title = test - | comparison #atomExpression +AND: 'AND'; +OR: 'OR'; +CONTAINS: 'CONTAINS'; +MATCHES: 'MATCHES'; +NOT: 'NOT'; + +FIELD: [A-Z]+; +STRING_LITERAL: '"' ('\\"' | ~["])* '"'; // " should be escaped with a backslash +TERM: ('\\' [=!~()] | ~[ \t\n\r=!~()])+; // =!~() should be escaped with a backslash + +start + : EOF + | andExpression EOF + ; + +andExpression + : expression+ #implicitAndExpression // example: author = miller year = 2010 --> equivalent to: author = miller AND year = 2010 + ; + +expression + : LPAREN andExpression RPAREN #parenExpression // example: (author = miller) + | NOT expression #negatedExpression // example: NOT author = miller + | left = expression bin_op = AND right = expression #binaryExpression // example: author = miller AND year = 2010 + | left = expression bin_op = OR right = expression #binaryExpression // example: author = miller OR year = 2010 + | comparison #comparisonExpression // example: miller OR author = miller + ; + +comparison + : FIELD operator searchValue // example: author = miller + | searchValue // example: miller ; -comparison: - left=name operator=(CONTAINS | MATCHES | EQUAL | EEQUAL | NEQUAL) right=name // example: author != miller - | right=name // example: miller (search all fields) +operator + : EQUAL + | CEQUAL + | EEQUAL + | CEEQUAL + | REQUAL + | CREEQUAL + | NEQUAL + | NCEQUAL + | NEEQUAL + | NCEEQUAL + | NREQUAL + | NCREEQUAL + | CONTAINS + | MATCHES ; -name: - STRING // example: "miller" - | FIELDTYPE // example: author +searchValue + : STRING_LITERAL + | FIELD + | TERM ; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a199ea29502..49906ad2625 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -97,6 +97,8 @@ // endregion // region: SQL databases + requires embedded.postgres; + requires org.tukaani.xz; requires ojdbc10; requires org.postgresql.jdbc; requires org.mariadb.jdbc; @@ -108,6 +110,7 @@ requires io.github.javadiffutils; requires java.string.similarity; requires org.apache.commons.cli; + requires org.apache.commons.compress; requires org.apache.commons.csv; requires org.apache.commons.io; requires org.apache.commons.lang3; @@ -160,8 +163,8 @@ // endregion // region: Lucene - /** - * In case the version is updated, please also increment {@link org.jabref.model.search.SearchFieldConstants#VERSION} to trigger reindexing. + /* + * In case the version is updated, please also increment {@link org.jabref.model.search.LinkedFilesConstants.VERSION} to trigger reindexing. */ uses org.apache.lucene.codecs.lucene100.Lucene100Codec; requires org.apache.lucene.analysis.common; diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index 8806c7892bf..a60a05bec30 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -9,6 +9,7 @@ import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.logic.UiCommand; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.search.PostgreServer; import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.migrations.PreferencesMigrations; @@ -38,6 +39,9 @@ public static void main(String[] args) { PreferencesMigrations.runMigrations(preferences); + PostgreServer postgreServer = new PostgreServer(); + Injector.setModelOrService(PostgreServer.class, postgreServer); + JabRefGUI.setup(uiCommands, preferences, fileUpdateMonitor); JabRefGUI.launch(JabRefGUI.class, args); } diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 1021049647a..3df190f9816 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -53,7 +53,7 @@ import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; @@ -458,8 +458,8 @@ private boolean exportMatches(List loaded) { List matches; try { - // extract current thread task executor from luceneManager - matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences.getFilePreferences()).getMatches(); + // extract current thread task executor from indexManager + matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences).getMatches(); } catch (IOException e) { LOGGER.error("Error occurred when searching", e); return false; diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 94c223eb6c0..be4054302ff 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -32,6 +32,7 @@ import org.jabref.logic.net.ProxyRegisterer; import org.jabref.logic.remote.RemotePreferences; import org.jabref.logic.remote.server.RemoteListenerServerManager; +import org.jabref.logic.search.PostgreServer; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.FallbackExceptionHandler; import org.jabref.logic.util.HeadlessExecutorService; @@ -395,6 +396,9 @@ public static void shutdownThreadPools() { LOGGER.trace("Shutting down directoryMonitor"); DirectoryMonitor directoryMonitor = Injector.instantiateModelOrService(DirectoryMonitor.class); directoryMonitor.shutdown(); + LOGGER.trace("Shutting down postgreServer"); + PostgreServer postgreServer = Injector.instantiateModelOrService(PostgreServer.class); + postgreServer.shutdown(); LOGGER.trace("Shutting down HeadlessExecutorService"); HeadlessExecutorService.INSTANCE.shutdownEverything(); LOGGER.trace("Finished shutdownThreadPools"); diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 3877db193e0..190ab74b520 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -73,7 +73,7 @@ import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; -import org.jabref.logic.search.LuceneManager; +import org.jabref.logic.search.IndexManager; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.TaskExecutor; @@ -93,9 +93,8 @@ import org.jabref.model.entry.event.FieldChangedEvent; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; import org.jabref.model.groups.GroupTreeNode; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; @@ -168,7 +167,7 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private final DirectoryMonitorManager directoryMonitorManager; private ImportHandler importHandler; - private LuceneManager luceneManager; + private IndexManager indexManager; private final AiService aiService; @@ -215,7 +214,7 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, private void initializeComponentsAndListeners(boolean isDummyContext) { if (!isDummyContext) { - createLuceneManager(); + createIndexManager(); } if (tableModel != null) { @@ -226,7 +225,7 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { bibDatabaseContext.getMetaData().registerListener(this); this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, stateManager, getLuceneManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); new CitationStyleCache(bibDatabaseContext); annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); @@ -322,17 +321,17 @@ private void onDatabaseLoadingSucceed(ParserResult result) { dataLoadingTask = null; } - public void createLuceneManager() { - luceneManager = new LuceneManager(bibDatabaseContext, taskExecutor, preferences.getFilePreferences()); - stateManager.setLuceneManager(bibDatabaseContext, luceneManager); + public void createIndexManager() { + indexManager = new IndexManager(bibDatabaseContext, taskExecutor, preferences); + stateManager.setIndexManager(bibDatabaseContext, indexManager); } - public LuceneManager getLuceneManager() { - return luceneManager; + public IndexManager getIndexManager() { + return indexManager; } - public void closeLuceneManger() { - luceneManager.close(); + public void closeIndexManger() { + indexManager.close(); } private void onDatabaseLoadingFailed(Exception ex) { @@ -794,11 +793,11 @@ private void onClosed(Event event) { LOGGER.error("Problem when closing directory monitor", e); } try { - if (luceneManager != null) { - luceneManager.close(); + if (indexManager != null) { + indexManager.close(); } } catch (RuntimeException e) { - LOGGER.error("Problem when closing lucene indexer", e); + LOGGER.error("Problem when closing index manager", e); } try { AutosaveManager.shutdown(bibDatabaseContext); @@ -1151,17 +1150,17 @@ private class IndexUpdateListener { @Subscribe public void listen(EntriesAddedEvent addedEntryEvent) { - luceneManager.addToIndex(addedEntryEvent.getBibEntries()); + indexManager.addToIndex(addedEntryEvent.getBibEntries()); } @Subscribe public void listen(EntriesRemovedEvent removedEntriesEvent) { - luceneManager.removeFromIndex(removedEntriesEvent.getBibEntries()); + indexManager.removeFromIndex(removedEntriesEvent.getBibEntries()); } @Subscribe public void listen(FieldChangedEvent fieldChangedEvent) { - luceneManager.updateEntry(fieldChangedEvent.getBibEntry(), fieldChangedEvent.getOldValue(), fieldChangedEvent.getNewValue(), fieldChangedEvent.getField().equals(StandardField.FILE)); + indexManager.updateEntry(fieldChangedEvent); } } diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index e75ffcdc89e..435c018619d 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -27,12 +27,12 @@ import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.DialogWindowState; import org.jabref.gui.util.OptionalObjectProperty; -import org.jabref.logic.search.LuceneManager; +import org.jabref.logic.search.IndexManager; import org.jabref.logic.util.BackgroundTask; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.EasyBinding; @@ -61,7 +61,7 @@ public class StateManager { private final OptionalObjectProperty activeTab = OptionalObjectProperty.empty(); private final ObservableList selectedEntries = FXCollections.observableArrayList(); private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); - private final ObservableMap luceneManagers = FXCollections.observableHashMap(); + private final ObservableMap indexManagers = FXCollections.observableHashMap(); private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); private final OptionalObjectProperty activeGlobalSearchQuery = OptionalObjectProperty.empty(); private final IntegerProperty searchResultSize = new SimpleIntegerProperty(0); @@ -128,12 +128,12 @@ public void clearSelectedGroups(BibDatabaseContext context) { selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).clear(); } - public void setLuceneManager(BibDatabaseContext database, LuceneManager luceneManager) { - luceneManagers.put(database.getUid(), luceneManager); + public void setIndexManager(BibDatabaseContext database, IndexManager indexManager) { + indexManagers.put(database.getUid(), indexManager); } - public Optional getLuceneManager(BibDatabaseContext database) { - return Optional.ofNullable(luceneManagers.get(database.getUid())); + public Optional getIndexManager(BibDatabaseContext database) { + return Optional.ofNullable(indexManagers.get(database.getUid())); } public Optional getActiveDatabase() { diff --git a/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java index f8d7f173acd..7f9c93b70af 100644 --- a/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java @@ -35,7 +35,7 @@ public void execute() { if (action == StandardActions.COPY_DOI_URL) { copy(DOI.parse(identifier).map(DOI::getURIAsASCIIString), identifier); } else { - copy(DOI.parse(identifier).map(DOI::getDOI), identifier); + copy(DOI.parse(identifier).map(DOI::asString), identifier); } } diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 0ef4ea2b74a..85bc9e41efd 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -115,7 +115,7 @@ private void copyDoi() { } else { copyDoiList(entries.stream() .filter(entry -> entry.getDOI().isPresent()) - .map(entry -> entry.getDOI().get().getDOI()) + .map(entry -> entry.getDOI().get().asString()) .collect(Collectors.toList()), entries.size()); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java index 428052847bf..1930d618d9c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java @@ -20,6 +20,7 @@ import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.fieldeditors.FieldEditorFX; import org.jabref.gui.fieldeditors.FieldNameLabel; +import org.jabref.gui.fieldeditors.MarkdownEditor; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.theme.ThemeManager; @@ -28,14 +29,13 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UserSpecificCommentField; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class CommentsTab extends FieldsEditorTab { public static final String NAME = "Comments"; @@ -55,7 +55,6 @@ public CommentsTab(GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super( false, @@ -69,7 +68,6 @@ public CommentsTab(GuiPreferences preferences, themeManager, taskExecutor, journalAbbreviationRepository, - luceneManager, searchQueryProperty ); this.defaultOwner = preferences.getOwnerPreferences().getDefaultOwner().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "-"); @@ -135,12 +133,12 @@ protected void setupPanel(BibEntry entry, boolean compressed) { Optional fieldEditorForUserDefinedComment = editors.entrySet().stream().filter(f -> f.getKey().getName().contains(defaultOwner)).map(Map.Entry::getValue).findFirst(); for (Map.Entry fieldEditorEntry : editors.entrySet()) { Field field = fieldEditorEntry.getKey(); - FieldEditorFX editor = fieldEditorEntry.getValue(); + MarkdownEditor editor = (MarkdownEditor) fieldEditorEntry.getValue().getNode(); boolean isStandardBibtexComment = field == StandardField.COMMENT; boolean isDefaultOwnerComment = field.equals(userSpecificCommentField); boolean shouldBeEnabled = isStandardBibtexComment || isDefaultOwnerComment; - editor.getNode().setDisable(!shouldBeEnabled); + editor.setEditable(shouldBeEnabled); } // Show "Hide" button only if user-specific comment field is empty. Otherwise, it is a strange UI, because the diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 45248e89dfe..c4f75a09893 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -19,7 +19,6 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -27,7 +26,7 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import com.tobiasdiez.easybind.EasyBind; @@ -47,9 +46,8 @@ public DeprecatedFieldsTab(BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { - super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, luceneManager, searchQueryProperty); + super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, searchQueryProperty); this.entryTypesManager = entryTypesManager; setText(Localization.lang("Deprecated fields")); diff --git a/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java index 4458d80a405..a10d6aff476 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java @@ -11,11 +11,10 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class DetailOptionalFieldsTab extends OptionalFieldsTabBase { @@ -32,7 +31,6 @@ public DetailOptionalFieldsTab(BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super( Localization.lang("Optional fields 2"), @@ -48,7 +46,6 @@ public DetailOptionalFieldsTab(BibDatabaseContext databaseContext, entryTypesManager, taskExecutor, journalAbbreviationRepository, - luceneManager, searchQueryProperty ); } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 6a382a7a59e..d2689b2e8b7 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -47,6 +47,7 @@ import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.RedoAction; import org.jabref.gui.undo.UndoAction; +import org.jabref.gui.util.DragDrop; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.ai.AiService; import org.jabref.logic.bibtex.TypedBibEntry; @@ -171,21 +172,11 @@ private void setupDragAndDrop(LibraryTab libraryTab) { boolean success = false; if (event.getDragboard().hasContent(DataFormat.FILES)) { - List draggedFiles = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - switch (event.getTransferMode()) { - case COPY -> { - LOGGER.debug("Mode COPY"); - fileLinker.copyFilesToFileDirAndAddToEntry(entry, draggedFiles, libraryTab.getLuceneManager()); - } - case MOVE -> { - LOGGER.debug("Mode MOVE"); - fileLinker.moveFilesToFileDirRenameAndAddToEntry(entry, draggedFiles, libraryTab.getLuceneManager()); - } - case LINK -> { - LOGGER.debug("Mode LINK"); - fileLinker.addFilesToEntry(entry, draggedFiles); - } - } + TransferMode transferMode = event.getTransferMode(); + List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + // Modifiers do not work on macOS: https://bugs.openjdk.org/browse/JDK-8264172 + // Similar code as org.jabref.gui.externalfiles.ImportHandler.importFilesInBackground + DragDrop.handleDropOfFiles(files, transferMode, fileLinker, entry); success = true; } @@ -276,21 +267,21 @@ private void navigateToNextEntry() { private List createTabs() { List tabs = new LinkedList<>(); - tabs.add(new PreviewTab(databaseContext, dialogService, preferences, themeManager, taskExecutor, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add(new PreviewTab(databaseContext, dialogService, preferences, themeManager, taskExecutor, libraryTab.searchQueryProperty())); // Required, optional (important+detail), deprecated, and "other" fields - tabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new ImportantOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new DetailOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); + tabs.add(new ImportantOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); + tabs.add(new DetailOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); + tabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); + tabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); // Comment Tab: Tab for general and user-specific comments - tabs.add(new CommentsTab(preferences, databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add(new CommentsTab(preferences, databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); Map> entryEditorTabList = getAdditionalUserConfiguredTabs(); for (Map.Entry> tab : entryEditorTabList.entrySet()) { - tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.searchQueryProperty())); } tabs.add(new MathSciNetTab()); diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 41b29ee8f42..4814dbb2776 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -36,12 +36,11 @@ import org.jabref.gui.undo.UndoAction; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.Subscription; @@ -64,7 +63,6 @@ abstract class FieldsEditorTab extends EntryEditorTab implements OffersPreview { private final JournalAbbreviationRepository journalAbbreviationRepository; private PreviewPanel previewPanel; private final UndoManager undoManager; - private final LuceneManager luceneManager; private final OptionalObjectProperty searchQueryProperty; private Collection fields = new ArrayList<>(); @SuppressWarnings("FieldCanBeLocal") @@ -81,7 +79,6 @@ public FieldsEditorTab(boolean compressed, ThemeManager themeManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { this.isCompressed = compressed; this.databaseContext = Objects.requireNonNull(databaseContext); @@ -94,7 +91,6 @@ public FieldsEditorTab(boolean compressed, this.themeManager = themeManager; this.taskExecutor = Objects.requireNonNull(taskExecutor); this.journalAbbreviationRepository = Objects.requireNonNull(journalAbbreviationRepository); - this.luceneManager = luceneManager; this.searchQueryProperty = searchQueryProperty; } @@ -265,7 +261,6 @@ private void initPanel() { preferences, themeManager, taskExecutor, - luceneManager, searchQueryProperty); EasyBind.subscribe(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), show -> { if (show) { diff --git a/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java index 64004b70848..4695bbeea73 100644 --- a/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java @@ -11,11 +11,10 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class ImportantOptionalFieldsTab extends OptionalFieldsTabBase { @@ -32,7 +31,6 @@ public ImportantOptionalFieldsTab(BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super( Localization.lang("Optional fields"), @@ -48,7 +46,6 @@ public ImportantOptionalFieldsTab(BibDatabaseContext databaseContext, entryTypesManager, taskExecutor, journalAbbreviationRepository, - luceneManager, searchQueryProperty ); } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java index 6e45f1d491e..7114b863bba 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -56,8 +56,10 @@ public LatexCitationsTab(BibDatabaseContext databaseContext, private void setSearchPane() { progressIndicator.setMaxSize(100, 100); + citationsDisplay.basePathProperty().bindBidirectional(viewModel.directoryProperty()); citationsDisplay.setItems(viewModel.getCitationList()); + citationsDisplay.setOnMouseClicked(event -> viewModel.handleMouseClick(event, citationsDisplay)); RowConstraints mainRow = new RowConstraints(); mainRow.setVgrow(Priority.ALWAYS); diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java index b4fa51069b3..dd4b46bbec0 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -15,10 +15,16 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.push.PushToApplication; +import org.jabref.gui.push.PushToApplications; +import org.jabref.gui.push.PushToTeXstudio; +import org.jabref.gui.texparser.CitationsDisplay; import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.l10n.Localization; @@ -154,6 +160,22 @@ public void onDirectoryDelete(File directory) { }; } + public void handleMouseClick(MouseEvent event, CitationsDisplay citationsDisplay) { + Citation selectedItem = citationsDisplay.getSelectionModel().getSelectedItem(); + + if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2 && selectedItem != null) { + String applicationName = preferences.getPushToApplicationPreferences() + .getActiveApplicationName(); + PushToApplication application = PushToApplications.getApplicationByName( + applicationName, + dialogService, + preferences) + .orElse(new PushToTeXstudio(dialogService, preferences)); + preferences.getPushToApplicationPreferences().setActiveApplicationName(application.getDisplayName()); + application.jumpToLine(selectedItem.path(), selectedItem.line(), selectedItem.colStart()); + } + } + public void bindToEntry(BibEntry entry) { checkAndUpdateDirectory(); diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java index 0d59a31bbf9..5775992cdb6 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java @@ -18,7 +18,6 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -26,7 +25,7 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class OptionalFieldsTabBase extends FieldsEditorTab { private final BibEntryTypesManager entryTypesManager; @@ -45,7 +44,6 @@ public OptionalFieldsTabBase(String title, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super(true, databaseContext, @@ -58,7 +56,6 @@ public OptionalFieldsTabBase(String title, themeManager, taskExecutor, journalAbbreviationRepository, - luceneManager, searchQueryProperty); this.entryTypesManager = entryTypesManager; this.isImportantOptionalFields = isImportantOptionalFields; diff --git a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java index 5fda9368885..c92ceae2eee 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java @@ -22,7 +22,6 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -33,7 +32,7 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UserSpecificCommentField; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class OtherFieldsTab extends FieldsEditorTab { @@ -52,7 +51,6 @@ public OtherFieldsTab(BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super(false, databaseContext, @@ -65,7 +63,6 @@ public OtherFieldsTab(BibDatabaseContext databaseContext, themeManager, taskExecutor, journalAbbreviationRepository, - luceneManager, searchQueryProperty); this.entryTypesManager = entryTypesManager; diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java index 52b51b67b7e..41242fea697 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java @@ -7,11 +7,10 @@ import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class PreviewTab extends EntryEditorTab implements OffersPreview { public static final String NAME = "Preview"; @@ -20,7 +19,6 @@ public class PreviewTab extends EntryEditorTab implements OffersPreview { private final GuiPreferences preferences; private final ThemeManager themeManager; private final TaskExecutor taskExecutor; - private final LuceneManager luceneManager; private final OptionalObjectProperty searchQueryProperty; private PreviewPanel previewPanel; @@ -29,14 +27,12 @@ public PreviewTab(BibDatabaseContext databaseContext, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { this.databaseContext = databaseContext; this.dialogService = dialogService; this.preferences = preferences; this.themeManager = themeManager; this.taskExecutor = taskExecutor; - this.luceneManager = luceneManager; this.searchQueryProperty = searchQueryProperty; setGraphic(IconTheme.JabRefIcons.TOGGLE_ENTRY_PREVIEW.getGraphicNode()); @@ -65,7 +61,7 @@ public boolean shouldShow(BibEntry entry) { @Override protected void bindToEntry(BibEntry entry) { if (previewPanel == null) { - previewPanel = new PreviewPanel(databaseContext, dialogService, preferences.getKeyBindingRepository(), preferences, themeManager, taskExecutor, luceneManager, searchQueryProperty); + previewPanel = new PreviewPanel(databaseContext, dialogService, preferences.getKeyBindingRepository(), preferences, themeManager, taskExecutor, searchQueryProperty); setContent(previewPanel); } diff --git a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java index 5966c329fda..b2bcc0ad5ef 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java @@ -18,7 +18,6 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -27,7 +26,7 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.OrFields; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class RequiredFieldsTab extends FieldsEditorTab { @@ -45,10 +44,9 @@ public RequiredFieldsTab(BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, - preferences, themeManager, taskExecutor, journalAbbreviationRepository, luceneManager, searchQueryProperty); + preferences, themeManager, taskExecutor, journalAbbreviationRepository, searchQueryProperty); this.entryTypesManager = entryTypesManager; setText(Localization.lang("Required fields")); setTooltip(new Tooltip(Localization.lang("Show required fields"))); diff --git a/src/main/java/org/jabref/gui/entryeditor/SciteTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/SciteTabViewModel.java index e592062bc93..0ad66d50c80 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SciteTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/SciteTabViewModel.java @@ -98,7 +98,7 @@ private void cancelSearch() { public SciteTallyModel fetchTallies(DOI doi) throws FetcherException { URL url; try { - url = new URI(BASE_URL + "tallies/" + doi.getDOI()).toURL(); + url = new URI(BASE_URL + "tallies/" + doi.asString()).toURL(); } catch (MalformedURLException | URISyntaxException ex) { throw new FetcherException("Malformed URL for DOI", ex); } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 26cf33bb55a..629a991e0e8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -3,11 +3,10 @@ import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.swing.undo.UndoManager; @@ -42,15 +41,16 @@ import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.os.OS; +import org.jabref.logic.search.retrieval.Highlighter; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.model.util.Range; import de.saxsys.mvvmfx.utils.validation.ObservableRuleBasedValidator; import de.saxsys.mvvmfx.utils.validation.ValidationMessage; @@ -63,6 +63,8 @@ public class SourceTab extends EntryEditorTab { private static final Logger LOGGER = LoggerFactory.getLogger(SourceTab.class); + private static final String TEXT_STYLE = "text"; + private static final String SEARCH_STYLE = "search"; private final FieldPreferences fieldPreferences; private final BibDatabaseMode mode; private final UndoManager undoManager; @@ -73,30 +75,11 @@ public class SourceTab extends EntryEditorTab { private final DialogService dialogService; private final BibEntryTypesManager entryTypesManager; private final KeyBindingRepository keyBindingRepository; - private Optional searchHighlightPattern = Optional.empty(); + private final OptionalObjectProperty searchQueryProperty; + private Map fieldPositions; private CodeArea codeArea; private BibEntry previousEntry; - private class EditAction extends SimpleCommand { - - private final StandardActions command; - - public EditAction(StandardActions command) { - this.command = command; - } - - @Override - public void execute() { - switch (command) { - case COPY -> codeArea.copy(); - case CUT -> codeArea.cut(); - case PASTE -> codeArea.paste(); - case SELECT_ALL -> codeArea.selectAll(); - } - codeArea.requestFocus(); - } - } - public SourceTab(BibDatabaseContext bibDatabaseContext, CountingUndoManager undoManager, FieldPreferences fieldPreferences, @@ -117,33 +100,57 @@ public SourceTab(BibDatabaseContext bibDatabaseContext, this.dialogService = dialogService; this.entryTypesManager = entryTypesManager; this.keyBindingRepository = keyBindingRepository; - - searchQueryProperty.addListener((observable, oldValue, newValue) -> { - searchHighlightPattern = newValue.flatMap(SearchQuery::getPatternForWords); - highlightSearchPattern(); - }); + this.searchQueryProperty = searchQueryProperty; + searchQueryProperty.addListener((observable, oldValue, newValue) -> highlightSearchPattern()); } private void highlightSearchPattern() { - if (codeArea != null) { - codeArea.setStyleClass(0, codeArea.getLength(), "text"); - if (searchHighlightPattern.isPresent()) { - Matcher matcher = searchHighlightPattern.get().matcher(codeArea.getText()); - while (matcher.find()) { - for (int i = 0; i <= matcher.groupCount(); i++) { - codeArea.setStyleClass(matcher.start(), matcher.end(), "search"); - } - } + if (codeArea == null || searchQueryProperty.get().isEmpty()) { + return; + } + + codeArea.setStyleClass(0, codeArea.getLength(), TEXT_STYLE); + Map, List> searchTermsMap = Highlighter.groupTermsByField(searchQueryProperty.get().get()); + + searchTermsMap.forEach((optionalField, terms) -> { + Optional searchPattern = Highlighter.buildSearchPattern(terms); + if (searchPattern.isEmpty()) { + return; + } + + if (optionalField.isPresent()) { + highlightField(optionalField.get(), searchPattern.get()); + } else { + fieldPositions.keySet().forEach(field -> highlightField(field, searchPattern.get())); } + }); + } + + private void highlightField(Field field, String searchPattern) { + Range fieldPosition = fieldPositions.get(field); + if (fieldPosition == null) { + return; + } + + int start = fieldPosition.start(); + int end = fieldPosition.end(); + List matchedPositions = Highlighter.findMatchPositions(codeArea.getText(start, end), searchPattern); + + for (Range range : matchedPositions) { + codeArea.setStyleClass(start + range.start() - 1, start + range.end(), SEARCH_STYLE); } } private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences) throws IOException { StringWriter writer = new StringWriter(); - BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + BibWriter bibWriter = new BibWriter(writer, "\n"); // JavaFX works with LF only FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); - new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); - return writer.toString(); + BibEntryWriter bibEntryWriter = new BibEntryWriter(fieldWriter, entryTypesManager); + bibEntryWriter.write(entry, bibWriter, type, true); + fieldPositions = bibEntryWriter.getFieldPositions(); + String sourceString = writer.toString(); + writer.close(); + return sourceString; } /* Work around for different input methods. @@ -338,12 +345,30 @@ private void storeSource(BibEntry outOfFocusEntry, String text) { private void listenForSaveKeybinding(KeyEvent event) { keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { - switch (binding) { - case SAVE_DATABASE, SAVE_ALL, SAVE_DATABASE_AS -> { - storeSource(currentEntry, codeArea.textProperty().getValue()); - } + case SAVE_DATABASE, SAVE_ALL, SAVE_DATABASE_AS -> + storeSource(currentEntry, codeArea.textProperty().getValue()); } }); } + + private class EditAction extends SimpleCommand { + + private final StandardActions command; + + public EditAction(StandardActions command) { + this.command = command; + } + + @Override + public void execute() { + switch (command) { + case COPY -> codeArea.copy(); + case CUT -> codeArea.cut(); + case PASTE -> codeArea.paste(); + case SELECT_ALL -> codeArea.selectAll(); + } + codeArea.requestFocus(); + } + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/UserDefinedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/UserDefinedFieldsTab.java index 3e09cdb59e8..05ae149e0d9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/UserDefinedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/UserDefinedFieldsTab.java @@ -15,12 +15,11 @@ import org.jabref.gui.undo.UndoAction; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.journals.JournalAbbreviationRepository; -import org.jabref.logic.search.LuceneManager; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import org.jabref.model.search.SearchQuery; +import org.jabref.model.search.query.SearchQuery; public class UserDefinedFieldsTab extends FieldsEditorTab { private final LinkedHashSet fields; @@ -37,9 +36,8 @@ public UserDefinedFieldsTab(String name, ThemeManager themeManager, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, OptionalObjectProperty searchQueryProperty) { - super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, luceneManager, searchQueryProperty); + super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, searchQueryProperty); this.fields = new LinkedHashSet<>(fields); diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java index 55888aa660f..de2f832b48f 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java @@ -15,26 +15,26 @@ public class BibEntryRelationsCache { private static final Map> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); public List getCitations(BibEntry entry) { - return CITATIONS_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList()); + return CITATIONS_MAP.getOrDefault(entry.getDOI().map(DOI::asString).orElse(""), Collections.emptyList()); } public List getReferences(BibEntry entry) { - return REFERENCES_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList()); + return REFERENCES_MAP.getOrDefault(entry.getDOI().map(DOI::asString).orElse(""), Collections.emptyList()); } public void cacheOrMergeCitations(BibEntry entry, List citations) { - entry.getDOI().ifPresent(doi -> CITATIONS_MAP.put(doi.getDOI(), citations)); + entry.getDOI().ifPresent(doi -> CITATIONS_MAP.put(doi.asString(), citations)); } public void cacheOrMergeReferences(BibEntry entry, List references) { - entry.getDOI().ifPresent(doi -> REFERENCES_MAP.putIfAbsent(doi.getDOI(), references)); + entry.getDOI().ifPresent(doi -> REFERENCES_MAP.putIfAbsent(doi.asString(), references)); } public boolean citationsCached(BibEntry entry) { - return CITATIONS_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse("")); + return CITATIONS_MAP.containsKey(entry.getDOI().map(DOI::asString).orElse("")); } public boolean referencesCached(BibEntry entry) { - return REFERENCES_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse("")); + return REFERENCES_MAP.containsKey(entry.getDOI().map(DOI::asString).orElse("")); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java index a183f7082e4..3267ed44f31 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java @@ -25,7 +25,7 @@ public SemanticScholarFetcher(ImporterPreferences importerPreferences) { } public String getAPIUrl(String entry_point, BibEntry entry) { - return SEMANTIC_SCHOLAR_API + "paper/" + "DOI:" + entry.getDOI().orElseThrow().getDOI() + "/" + entry_point + return SEMANTIC_SCHOLAR_API + "paper/" + "DOI:" + entry.getDOI().orElseThrow().asString() + "/" + entry_point + "?fields=" + "title,authors,year,citationCount,referenceCount,externalIds,publicationTypes,abstract,url" + "&limit=1000"; } diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java index 01441568e60..5e98f7d737d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -34,9 +34,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.search.SearchFlags; -import org.jabref.model.search.SearchQuery; -import org.jabref.model.search.SearchResult; -import org.jabref.model.search.SearchResults; +import org.jabref.model.search.query.SearchQuery; +import org.jabref.model.search.query.SearchResult; +import org.jabref.model.search.query.SearchResults; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 6a2dd9cb42b..10487ead6ad 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -139,10 +139,10 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { Optional databasePath = context.getDatabasePath(); if (databasePath.isPresent()) { - // Close AutosaveManager, BackupManager, and LuceneManager for original library + // Close AutosaveManager, BackupManager, and IndexManager for original library AutosaveManager.shutdown(context); BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); - libraryTab.closeLuceneManger(); + libraryTab.closeIndexManger(); } // Set new location @@ -160,10 +160,10 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { context.setDatabasePath(file); libraryTab.updateTabTitle(false); - // Reset (here: uninstall and install again) AutosaveManager, BackupManager and LuceneManager for the new file name + // Reset (here: uninstall and install again) AutosaveManager, BackupManager and IndexManager for the new file name libraryTab.resetChangeMonitor(); libraryTab.installAutosaveManagerAndBackupManager(); - libraryTab.createLuceneManager(); + libraryTab.createIndexManager(); preferences.getLastFilesOpenedPreferences().getFileHistory().newFile(file); } diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java index 85b1a83213e..26fde2c5ba6 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -1,24 +1,18 @@ package org.jabref.gui.externalfiles; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Optional; +import java.util.stream.Stream; -import org.jabref.gui.DialogService; -import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.logic.FilePreferences; -import org.jabref.logic.cleanup.MoveFilesCleanup; -import org.jabref.logic.cleanup.RenamePdfCleanup; +import org.jabref.logic.externalfiles.LinkedFileHandler; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.search.LuceneManager; -import org.jabref.logic.util.io.FileNameCleaner; +import org.jabref.logic.util.NotificationService; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -34,100 +28,68 @@ public class ExternalFilesEntryLinker { private final ExternalApplicationsPreferences externalApplicationsPreferences; private final FilePreferences filePreferences; private final BibDatabaseContext bibDatabaseContext; - private final MoveFilesCleanup moveFilesCleanup; - private final RenamePdfCleanup renameFilesCleanup; - private final DialogService dialogService; + private final NotificationService notificationService; - public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, BibDatabaseContext bibDatabaseContext, DialogService dialogService) { + public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, BibDatabaseContext bibDatabaseContext, NotificationService notificationService) { this.externalApplicationsPreferences = externalApplicationsPreferences; this.filePreferences = filePreferences; this.bibDatabaseContext = bibDatabaseContext; - this.moveFilesCleanup = new MoveFilesCleanup(bibDatabaseContext, filePreferences); - this.renameFilesCleanup = new RenamePdfCleanup(false, bibDatabaseContext, filePreferences); - this.dialogService = dialogService; + this.notificationService = notificationService; } - public Optional copyFileToFileDir(Path file) { - Optional firstExistingFileDir = bibDatabaseContext.getFirstExistingFileDir(filePreferences); - if (firstExistingFileDir.isPresent()) { - Path targetFile = firstExistingFileDir.get().resolve(file.getFileName()); - if (FileUtil.copyFile(file, targetFile, false)) { - return Optional.of(targetFile); + public void linkFilesToEntry(BibEntry entry, List files) { + List existingFiles = entry.getFiles(); + List linkedFiles = files.stream().flatMap(file -> { + String typeName = FileUtil.getFileExtension(file) + .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences).orElse(new UnknownExternalFileType(ext)).getName()) + .orElse(""); + Path relativePath = FileUtil.relativize(file, bibDatabaseContext, filePreferences); + LinkedFile linkedFile = new LinkedFile("", relativePath, typeName); + + String link = linkedFile.getLink(); + boolean alreadyLinked = existingFiles.stream().anyMatch(existingFile -> existingFile.getLink().equals(link)); + if (alreadyLinked) { + notificationService.notify(Localization.lang("File '%0' already linked", link)); + return Stream.empty(); + } else { + return Stream.of(linkedFile); } - } - return Optional.empty(); - } - - public void renameLinkedFilesToPattern(BibEntry entry) { - renameFilesCleanup.cleanup(entry); - } - - public void moveLinkedFilesToFileDir(BibEntry entry) { - moveFilesCleanup.cleanup(entry); - } - - public void addFilesToEntry(BibEntry entry, List files) { - List validFiles = getValidFileNames(files); - for (Path file : validFiles) { - FileUtil.getFileExtension(file).ifPresent(ext -> { - ExternalFileType type = ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences) - .orElse(new UnknownExternalFileType(ext)); - Path relativePath = FileUtil.relativize(file, bibDatabaseContext, filePreferences); - LinkedFile linkedfile = new LinkedFile("", relativePath, type.getName()); - entry.addFile(linkedfile); - }); - } + }).toList(); + entry.addFiles(linkedFiles); } - public void moveFilesToFileDirRenameAndAddToEntry(BibEntry entry, List files, LuceneManager luceneManager) { - try (AutoCloseable blocker = luceneManager.blockLinkedFileIndexer()) { - addFilesToEntry(entry, files); - moveLinkedFilesToFileDir(entry); - renameLinkedFilesToPattern(entry); - } catch (Exception e) { - LOGGER.error("Could not block LinkedFilesIndexer", e); - } - luceneManager.updateAfterDropFiles(entry); - } - - public void copyFilesToFileDirAndAddToEntry(BibEntry entry, List files, LuceneManager luceneManager) { - try (AutoCloseable blocker = luceneManager.blockLinkedFileIndexer()) { - for (Path file : files) { - copyFileToFileDir(file) - .ifPresent(copiedFile -> addFilesToEntry(entry, Collections.singletonList(copiedFile))); + /** + *
    + *
  • Move files to file directory
  • + *
  • Use configured file directory pattern
  • + *
  • Rename file to configured pattern (and skip renaming if file already exists)
  • + *
  • Avoid overwriting files - by adding " {number}" after the file name
  • + *
+ */ + public void coveOrMoveFilesSteps(BibEntry entry, List files, boolean shouldMove) { + List existingFiles = entry.getFiles(); + List linkedFiles = new ArrayList<>(files.size()); + // "old school" loop to enable logging properly + for (Path file : files) { + String typeName = FileUtil.getFileExtension(file) + .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences).orElse(new UnknownExternalFileType(ext)).getName()) + .orElse(""); + LinkedFile linkedFile = new LinkedFile("", file, typeName); + LinkedFileHandler linkedFileHandler = new LinkedFileHandler(linkedFile, entry, bibDatabaseContext, filePreferences); + try { + linkedFileHandler.copyOrMoveToDefaultDirectory(shouldMove, true); + } catch (IOException exception) { + LOGGER.error("Error while copying/moving file {}", file, exception); } - renameLinkedFilesToPattern(entry); - } catch (Exception e) { - LOGGER.error("Could not block LinkedFilesIndexer", e); - } - luceneManager.updateAfterDropFiles(entry); - } - - private List getValidFileNames(List filesToAdd) { - List validFileNames = new ArrayList<>(); - - for (Path fileToAdd : filesToAdd) { - if (FileUtil.detectBadFileName(fileToAdd.toString())) { - String newFilename = FileNameCleaner.cleanFileName(fileToAdd.getFileName().toString()); - - boolean correctButtonPressed = dialogService.showConfirmationDialogAndWait(Localization.lang("File \"%0\" cannot be added!", fileToAdd.getFileName()), - Localization.lang("Illegal characters in the file name detected.\nFile will be renamed to \"%0\" and added.", newFilename), - Localization.lang("Rename and add")); - if (correctButtonPressed) { - Path correctPath = fileToAdd.resolveSibling(newFilename); - try { - Files.move(fileToAdd, correctPath); - validFileNames.add(correctPath); - } catch (IOException ex) { - LOGGER.error("Error moving file", ex); - dialogService.showErrorDialogAndWait(ex); - } - } + String link = linkedFile.getLink(); + boolean alreadyLinked = existingFiles.stream().anyMatch(existingFile -> existingFile.getLink().equals(link)); + if (alreadyLinked) { + notificationService.notify(Localization.lang("File '%0' already linked", link)); } else { - validFileNames.add(fileToAdd); + linkedFiles.add(linkedFile); } } - return validFileNames; + entry.addFiles(linkedFiles); } } diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index d3b621fe16e..43038d286f5 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -13,6 +13,8 @@ import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoManager; +import javafx.scene.input.TransferMode; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.duplicationFinder.DuplicateResolverDialog; @@ -20,6 +22,7 @@ import org.jabref.gui.libraryproperties.constants.ConstantsItemModel; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.gui.util.DragDrop; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.FilePreferences; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; @@ -32,9 +35,6 @@ import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.ImportFormatReader.UnknownFormatImport; import org.jabref.logic.importer.ParseException; -import org.jabref.logic.importer.fetcher.ArXivFetcher; -import org.jabref.logic.importer.fetcher.DoiFetcher; -import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BackgroundTask; @@ -49,9 +49,6 @@ import org.jabref.model.entry.BibtexString; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.identifier.ArXivIdentifier; -import org.jabref.model.entry.identifier.DOI; -import org.jabref.model.entry.identifier.ISBN; import org.jabref.model.groups.GroupEntryChanger; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.util.FileUpdateMonitor; @@ -70,7 +67,7 @@ public class ImportHandler { private final BibDatabaseContext bibDatabaseContext; private final GuiPreferences preferences; private final FileUpdateMonitor fileUpdateMonitor; - private final ExternalFilesEntryLinker linker; + private final ExternalFilesEntryLinker fileLinker; private final ExternalFilesContentImporter contentImporter; private final UndoManager undoManager; private final StateManager stateManager; @@ -92,16 +89,16 @@ public ImportHandler(BibDatabaseContext database, this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.linker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), database, dialogService); + this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), database, dialogService); this.contentImporter = new ExternalFilesContentImporter(preferences.getImportFormatPreferences()); this.undoManager = undoManager; } - public ExternalFilesEntryLinker getLinker() { - return linker; + public ExternalFilesEntryLinker getFileLinker() { + return fileLinker; } - public BackgroundTask> importFilesInBackground(final List files, final BibDatabaseContext bibDatabaseContext, final FilePreferences filePreferences) { + public BackgroundTask> importFilesInBackground(final List files, final BibDatabaseContext bibDatabaseContext, final FilePreferences filePreferences, TransferMode transferMode) { return new BackgroundTask<>() { private int counter; private final List results = new ArrayList<>(); @@ -131,23 +128,38 @@ public List call() { addResultToList(file, false, Localization.lang("Error reading PDF content: %0", pdfImporterResult.getErrorMessage())); } - if (!pdfEntriesInFile.isEmpty()) { - entriesToAdd.addAll(FileUtil.relativize(pdfEntriesInFile, bibDatabaseContext, filePreferences)); - addResultToList(file, true, Localization.lang("File was successfully imported as a new entry")); - } else { + if (pdfEntriesInFile.isEmpty()) { entriesToAdd.add(createEmptyEntryWithLink(file)); addResultToList(file, false, Localization.lang("No BibTeX was found. An empty entry was created with file link.")); + } else { + pdfEntriesInFile.forEach(entry -> { + if (entry.getFiles().size() > 1) { + LOGGER.warn("Entry has more than one file attached. This is not supported."); + LOGGER.warn("Entry's files: {}", entry.getFiles()); + } + entry.clearField(StandardField.FILE); + // Modifiers do not work on macOS: https://bugs.openjdk.org/browse/JDK-8264172 + // Similar code as org.jabref.gui.preview.PreviewPanel.PreviewPanel + DragDrop.handleDropOfFiles(files, transferMode, fileLinker, entry); + entriesToAdd.addAll(pdfEntriesInFile); + addResultToList(file, true, Localization.lang("File was successfully imported as a new entry")); + }); } } else if (FileUtil.isBibFile(file)) { var bibtexParserResult = contentImporter.importFromBibFile(file, fileUpdateMonitor); - if (bibtexParserResult.hasWarnings()) { - addResultToList(file, false, bibtexParserResult.getErrorMessage()); + List entries = bibtexParserResult.getDatabaseContext().getEntries(); + entriesToAdd.addAll(entries); + boolean success = !bibtexParserResult.hasWarnings(); + String message; + if (success) { + message = Localization.lang("Bib entry was successfully imported"); + } else { + message = bibtexParserResult.getErrorMessage(); } - - entriesToAdd.addAll(bibtexParserResult.getDatabaseContext().getEntries()); - addResultToList(file, true, Localization.lang("Bib entry was successfully imported")); + addResultToList(file, success, message); } else { - entriesToAdd.add(createEmptyEntryWithLink(file)); + BibEntry emptyEntryWithLink = createEmptyEntryWithLink(file); + entriesToAdd.add(emptyEntryWithLink); addResultToList(file, false, Localization.lang("No BibTeX data was found. An empty entry was created with file link.")); } } catch (IOException ex) { @@ -158,6 +170,7 @@ public List call() { } // We need to run the actual import on the FX Thread, otherwise we will get some deadlocks with the UIThreadList + // That method does a clone() on each entry UiTaskExecutor.runInJavaFXThread(() -> importEntries(entriesToAdd)); ce.addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entriesToAdd)); @@ -180,7 +193,7 @@ private void addResultToList(Path newFile, boolean success, String logMessage) { private BibEntry createEmptyEntryWithLink(Path file) { BibEntry entry = new BibEntry(); entry.setField(StandardField.TITLE, file.getFileName().toString()); - linker.addFilesToEntry(entry, Collections.singletonList(file)); + fileLinker.linkFilesToEntry(entry, Collections.singletonList(file)); return entry; } @@ -385,24 +398,6 @@ private List tryImportFormats(String data) { } } - private List fetchByDOI(DOI doi) throws FetcherException { - LOGGER.info("Found DOI identifier in clipboard"); - Optional entry = new DoiFetcher(preferences.getImportFormatPreferences()).performSearchById(doi.getDOI()); - return OptionalUtil.toList(entry); - } - - private List fetchByArXiv(ArXivIdentifier arXivIdentifier) throws FetcherException { - LOGGER.info("Found arxiv identifier in clipboard"); - Optional entry = new ArXivFetcher(preferences.getImportFormatPreferences()).performSearchById(arXivIdentifier.getNormalizedWithoutVersion()); - return OptionalUtil.toList(entry); - } - - private List fetchByISBN(ISBN isbn) throws FetcherException { - LOGGER.info("Found ISBN identifier in clipboard"); - Optional entry = new IsbnFetcher(preferences.getImportFormatPreferences()).performSearchById(isbn.getNormalized()); - return OptionalUtil.toList(entry); - } - public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List entriesToAdd) { boolean firstEntry = true; for (BibEntry entry : entriesToAdd) { diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java index ecee35887f3..e762eadda2f 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java @@ -27,6 +27,7 @@ import javafx.collections.ObservableList; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.TreeItem; +import javafx.scene.input.TransferMode; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -154,7 +155,7 @@ public void startImport() { } resultList.clear(); - importFilesBackgroundTask = importHandler.importFilesInBackground(fileList, bibDatabase, preferences.getFilePreferences()) + importFilesBackgroundTask = importHandler.importFilesInBackground(fileList, bibDatabase, preferences.getFilePreferences(), TransferMode.LINK) .onRunning(() -> { progressValueProperty.bind(importFilesBackgroundTask.workDonePercentageProperty()); progressTextProperty.bind(importFilesBackgroundTask.messageProperty()); diff --git a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java index 891757d43ca..7d230d3db2a 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java @@ -15,7 +15,6 @@ import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; public class MarkdownEditor extends SimpleEditor { - private final FlexmarkHtmlConverter flexmarkHtmlConverter = FlexmarkHtmlConverter.builder().build(); public MarkdownEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, GuiPreferences preferences, UndoManager undoManager, UndoAction undoAction, RedoAction redoAction) { @@ -37,4 +36,8 @@ public void paste() { } }; } + + public void setEditable(boolean isEditable) { + getTextInput().setEditable(isEditable); + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java index 89a6464d843..9b9537c967e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java @@ -72,4 +72,8 @@ public Parent getNode() { public void requestFocus() { textInput.requestFocus(); } + + protected TextInputControl getTextInput() { + return textInput; + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java index 7b86f1f2966..25df3942c8b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java @@ -48,7 +48,7 @@ public void lookupIdentifier(BibEntry bibEntry) { .onFinished(() -> identifierLookupInProgress.setValue(false)) .onSuccess(identifier -> { if (identifier.isPresent()) { - entry.setField(field, identifier.get().getNormalized()); + entry.setField(field, identifier.get().asString()); } else { dialogService.notify(Localization.lang("No %0 found", field.getDisplayName())); } @@ -66,7 +66,7 @@ public void fetchBibliographyInformation(BibEntry bibEntry) { @Override public void openExternalLink() { - identifier.get().map(DOI::getDOI) + identifier.get().map(DOI::asString) .ifPresent(s -> NativeDesktop.openCustomDoi(s, preferences, dialogService)); } } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index c40a946cffc..2b7ffb38497 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -524,7 +524,6 @@ private OpenDatabaseAction getOpenDatabaseAction() { * Refreshes the ui after preferences changes */ public void refresh() { - globalSearchBar.updateHintVisibility(); getLibraryTabs().forEach(LibraryTab::setupMainPanel); getLibraryTabs().forEach(tab -> tab.getMainTable().getTableModel().resetFieldFormatter()); } diff --git a/src/main/java/org/jabref/gui/frame/MainToolBar.java b/src/main/java/org/jabref/gui/frame/MainToolBar.java index bcbe2b8ba69..9cd83cd28f0 100644 --- a/src/main/java/org/jabref/gui/frame/MainToolBar.java +++ b/src/main/java/org/jabref/gui/frame/MainToolBar.java @@ -196,8 +196,8 @@ Group createTaskIndicator() { indicator.getStyleClass().add("progress-indicatorToolbar"); indicator.progressProperty().bind(stateManager.getTasksProgress()); - Tooltip someTasksRunning = new Tooltip(Localization.lang("Background Tasks are running")); - Tooltip noTasksRunning = new Tooltip(Localization.lang("Background Tasks are done")); + Tooltip someTasksRunning = new Tooltip(Localization.lang("Background tasks are running")); + Tooltip noTasksRunning = new Tooltip(Localization.lang("Background tasks are finished")); indicator.setTooltip(noTasksRunning); stateManager.getAnyTaskRunning().addListener((observable, oldValue, newValue) -> { if (newValue) { @@ -235,7 +235,7 @@ Group createTaskIndicator() { if (progressViewPopOver == null) { progressViewPopOver = new PopOver(taskProgressView); - progressViewPopOver.setTitle(Localization.lang("Background Tasks")); + progressViewPopOver.setTitle(Localization.lang("Background tasks")); progressViewPopOver.setArrowLocation(PopOver.ArrowLocation.RIGHT_TOP); } diff --git a/src/main/java/org/jabref/gui/groups/GroupDialog.fxml b/src/main/java/org/jabref/gui/groups/GroupDialog.fxml index 75e33d5f0ae..c045779fa7d 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialog.fxml +++ b/src/main/java/org/jabref/gui/groups/GroupDialog.fxml @@ -127,6 +127,18 @@